@caupulican/pi-agent-core 0.81.42 → 0.84.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (92) hide show
  1. package/dist/agent-loop.d.ts +3 -2
  2. package/dist/agent-loop.d.ts.map +1 -1
  3. package/dist/agent-loop.js +127 -33
  4. package/dist/agent-loop.js.map +1 -1
  5. package/dist/agent.d.ts +15 -1
  6. package/dist/agent.d.ts.map +1 -1
  7. package/dist/agent.js +9 -38
  8. package/dist/agent.js.map +1 -1
  9. package/dist/compaction/branch-summarization.d.ts +1 -1
  10. package/dist/compaction/branch-summarization.d.ts.map +1 -1
  11. package/dist/compaction/branch-summarization.js +3 -13
  12. package/dist/compaction/branch-summarization.js.map +1 -1
  13. package/dist/compaction/compaction.d.ts +1 -1
  14. package/dist/compaction/compaction.d.ts.map +1 -1
  15. package/dist/compaction/compaction.js +26 -57
  16. package/dist/compaction/compaction.js.map +1 -1
  17. package/dist/compaction/extraction.d.ts.map +1 -1
  18. package/dist/compaction/extraction.js +1 -6
  19. package/dist/compaction/extraction.js.map +1 -1
  20. package/dist/compaction/index.d.ts.map +1 -1
  21. package/dist/compaction/loop.d.ts.map +1 -1
  22. package/dist/compaction/loop.js.map +1 -1
  23. package/dist/compaction/token-budget.d.ts.map +1 -1
  24. package/dist/compaction/token-budget.js +7 -5
  25. package/dist/compaction/token-budget.js.map +1 -1
  26. package/dist/compaction/utils.d.ts +5 -0
  27. package/dist/compaction/utils.d.ts.map +1 -1
  28. package/dist/compaction/utils.js +20 -0
  29. package/dist/compaction/utils.js.map +1 -1
  30. package/dist/compaction/verification.d.ts.map +1 -1
  31. package/dist/compaction/verification.js +6 -5
  32. package/dist/compaction/verification.js.map +1 -1
  33. package/dist/index.d.ts.map +1 -1
  34. package/dist/messages.d.ts.map +1 -1
  35. package/dist/messages.js.map +1 -1
  36. package/dist/node.d.ts.map +1 -1
  37. package/dist/proxy.d.ts +3 -2
  38. package/dist/proxy.d.ts.map +1 -1
  39. package/dist/proxy.js +88 -56
  40. package/dist/proxy.js.map +1 -1
  41. package/dist/reliability/classifier.d.ts.map +1 -1
  42. package/dist/reliability/classifier.js.map +1 -1
  43. package/dist/reliability/index.d.ts.map +1 -1
  44. package/dist/reliability/node.d.ts.map +1 -1
  45. package/dist/reliability/process-tree.d.ts.map +1 -1
  46. package/dist/reliability/process-tree.js.map +1 -1
  47. package/dist/reliability/provider-signatures.d.ts.map +1 -1
  48. package/dist/reliability/retry-controller.d.ts +1 -1
  49. package/dist/reliability/retry-controller.d.ts.map +1 -1
  50. package/dist/reliability/retry-controller.js +2 -7
  51. package/dist/reliability/retry-controller.js.map +1 -1
  52. package/dist/reliability/retry.d.ts.map +1 -1
  53. package/dist/reliability/retry.js.map +1 -1
  54. package/dist/reliability/watchdogs.d.ts.map +1 -1
  55. package/dist/reliability/watchdogs.js +24 -41
  56. package/dist/reliability/watchdogs.js.map +1 -1
  57. package/dist/session/message-retention.d.ts.map +1 -1
  58. package/dist/session/message-retention.js.map +1 -1
  59. package/dist/session/session-manager.d.ts +1 -1
  60. package/dist/session/session-manager.d.ts.map +1 -1
  61. package/dist/session/session-manager.js +81 -43
  62. package/dist/session/session-manager.js.map +1 -1
  63. package/dist/tool-failure-memory.d.ts.map +1 -1
  64. package/dist/tool-failure-memory.js +1 -1
  65. package/dist/tool-failure-memory.js.map +1 -1
  66. package/dist/types.d.ts +41 -0
  67. package/dist/types.d.ts.map +1 -1
  68. package/dist/types.js.map +1 -1
  69. package/dist/usage.d.ts +3 -2
  70. package/dist/usage.d.ts.map +1 -1
  71. package/dist/usage.js +2 -10
  72. package/dist/usage.js.map +1 -1
  73. package/dist/utils/paths.d.ts.map +1 -1
  74. package/dist/utils/paths.js.map +1 -1
  75. package/dist/utils/shell-output.d.ts.map +1 -1
  76. package/dist/utils/shell-output.js.map +1 -1
  77. package/dist/utils/streaming-content.d.ts +23 -0
  78. package/dist/utils/streaming-content.d.ts.map +1 -0
  79. package/dist/utils/streaming-content.js +52 -0
  80. package/dist/utils/streaming-content.js.map +1 -0
  81. package/dist/utils/truncate.d.ts.map +1 -1
  82. package/dist/utils/truncate.js +55 -80
  83. package/dist/utils/truncate.js.map +1 -1
  84. package/dist/uuid.d.ts +1 -1
  85. package/dist/uuid.d.ts.map +1 -1
  86. package/dist/uuid.js +1 -1
  87. package/dist/uuid.js.map +1 -1
  88. package/package.json +7 -7
  89. package/dist/utils/streaming-lines.d.ts +0 -20
  90. package/dist/utils/streaming-lines.d.ts.map +0 -1
  91. package/dist/utils/streaming-lines.js +0 -79
  92. package/dist/utils/streaming-lines.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"shell-output.js","sourceRoot":"","sources":["../../src/utils/shell-output.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,MAAM,UAAU,oBAAoB,CAAC,GAAW,EAAU;IACzD,OAAO,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC;SACpB,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC;QACjB,yDAAyD;QACzD,iBAAiB;QACjB,8BAA8B;QAC9B,oBAAoB;QACpB,kCAAkC;QAClC,0CAA0C;QAE1C,MAAM,IAAI,GAAG,IAAI,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC;QAEjC,mEAAmE;QACnE,IAAI,IAAI,KAAK,SAAS;YAAE,OAAO,KAAK,CAAC;QAErC,sCAAsC;QACtC,IAAI,IAAI,KAAK,IAAI,IAAI,IAAI,KAAK,IAAI,IAAI,IAAI,KAAK,IAAI;YAAE,OAAO,IAAI,CAAC;QAEjE,sFAAsF;QACtF,IAAI,IAAI,IAAI,IAAI,IAAI,IAAI,KAAK,IAAI,IAAI,CAAC,IAAI,IAAI,IAAI,IAAI,IAAI,IAAI,IAAI,CAAC;YAAE,OAAO,KAAK,CAAC;QAElF,6FAA6F;QAC7F,yFAAyF;QACzF,IAAI,IAAI,IAAI,MAAM,IAAI,IAAI,IAAI,MAAM;YAAE,OAAO,KAAK,CAAC;QAEnD,uCAAuC;QACvC,IAAI,IAAI,IAAI,MAAM,IAAI,IAAI,IAAI,MAAM;YAAE,OAAO,KAAK,CAAC;QAEnD,OAAO,IAAI,CAAC;IAAA,CACZ,CAAC;SACD,IAAI,CAAC,EAAE,CAAC,CAAC;AAAA,CACX","sourcesContent":["/**\n * Sanitize binary output for display/storage.\n * Removes characters that crash string-width or cause display issues:\n * - Control characters (except tab, newline, carriage return)\n * - Lone surrogates\n * - Unicode Format characters (crash string-width due to a bug)\n * - Characters with undefined code points\n */\nexport function sanitizeBinaryOutput(str: string): string {\n\treturn Array.from(str)\n\t\t.filter((char) => {\n\t\t\t// Filter out characters that cause string-width to crash\n\t\t\t// This includes:\n\t\t\t// - Unicode format characters\n\t\t\t// - Lone surrogates\n\t\t\t// - Control chars except \\t \\n \\r\n\t\t\t// - Characters with undefined code points\n\n\t\t\tconst code = char.codePointAt(0);\n\n\t\t\t// Skip if code point is undefined (edge case with invalid strings)\n\t\t\tif (code === undefined) return false;\n\n\t\t\t// Allow tab, newline, carriage return\n\t\t\tif (code === 0x09 || code === 0x0a || code === 0x0d) return true;\n\n\t\t\t// Filter out control characters (0x00-0x1F, 0x7F, 0x80-0x9F; except 0x09, 0x0a, 0x0d)\n\t\t\tif (code <= 0x1f || code === 0x7f || (code >= 0x80 && code <= 0x9f)) return false;\n\n\t\t\t// Filter out lone surrogate code units. Valid surrogate pairs are emitted by Array.from as a\n\t\t\t// two-code-unit string whose code point is outside the surrogate range, so they survive.\n\t\t\tif (code >= 0xd800 && code <= 0xdfff) return false;\n\n\t\t\t// Filter out Unicode format characters\n\t\t\tif (code >= 0xfff9 && code <= 0xfffb) return false;\n\n\t\t\treturn true;\n\t\t})\n\t\t.join(\"\");\n}\n"]}
1
+ {"version":3,"file":"shell-output.js","sourceRoot":"","sources":["../../src/utils/shell-output.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,MAAM,UAAU,oBAAoB,CAAC,GAAW;IAC/C,OAAO,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC;SACpB,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE;QAChB,yDAAyD;QACzD,iBAAiB;QACjB,8BAA8B;QAC9B,oBAAoB;QACpB,kCAAkC;QAClC,0CAA0C;QAE1C,MAAM,IAAI,GAAG,IAAI,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC;QAEjC,mEAAmE;QACnE,IAAI,IAAI,KAAK,SAAS;YAAE,OAAO,KAAK,CAAC;QAErC,sCAAsC;QACtC,IAAI,IAAI,KAAK,IAAI,IAAI,IAAI,KAAK,IAAI,IAAI,IAAI,KAAK,IAAI;YAAE,OAAO,IAAI,CAAC;QAEjE,sFAAsF;QACtF,IAAI,IAAI,IAAI,IAAI,IAAI,IAAI,KAAK,IAAI,IAAI,CAAC,IAAI,IAAI,IAAI,IAAI,IAAI,IAAI,IAAI,CAAC;YAAE,OAAO,KAAK,CAAC;QAElF,6FAA6F;QAC7F,yFAAyF;QACzF,IAAI,IAAI,IAAI,MAAM,IAAI,IAAI,IAAI,MAAM;YAAE,OAAO,KAAK,CAAC;QAEnD,uCAAuC;QACvC,IAAI,IAAI,IAAI,MAAM,IAAI,IAAI,IAAI,MAAM;YAAE,OAAO,KAAK,CAAC;QAEnD,OAAO,IAAI,CAAC;IACb,CAAC,CAAC;SACD,IAAI,CAAC,EAAE,CAAC,CAAC;AACZ,CAAC","sourcesContent":["/**\n * Sanitize binary output for display/storage.\n * Removes characters that crash string-width or cause display issues:\n * - Control characters (except tab, newline, carriage return)\n * - Lone surrogates\n * - Unicode Format characters (crash string-width due to a bug)\n * - Characters with undefined code points\n */\nexport function sanitizeBinaryOutput(str: string): string {\n\treturn Array.from(str)\n\t\t.filter((char) => {\n\t\t\t// Filter out characters that cause string-width to crash\n\t\t\t// This includes:\n\t\t\t// - Unicode format characters\n\t\t\t// - Lone surrogates\n\t\t\t// - Control chars except \\t \\n \\r\n\t\t\t// - Characters with undefined code points\n\n\t\t\tconst code = char.codePointAt(0);\n\n\t\t\t// Skip if code point is undefined (edge case with invalid strings)\n\t\t\tif (code === undefined) return false;\n\n\t\t\t// Allow tab, newline, carriage return\n\t\t\tif (code === 0x09 || code === 0x0a || code === 0x0d) return true;\n\n\t\t\t// Filter out control characters (0x00-0x1F, 0x7F, 0x80-0x9F; except 0x09, 0x0a, 0x0d)\n\t\t\tif (code <= 0x1f || code === 0x7f || (code >= 0x80 && code <= 0x9f)) return false;\n\n\t\t\t// Filter out lone surrogate code units. Valid surrogate pairs are emitted by Array.from as a\n\t\t\t// two-code-unit string whose code point is outside the surrogate range, so they survive.\n\t\t\tif (code >= 0xd800 && code <= 0xdfff) return false;\n\n\t\t\t// Filter out Unicode format characters\n\t\t\tif (code >= 0xfff9 && code <= 0xfffb) return false;\n\n\t\t\treturn true;\n\t\t})\n\t\t.join(\"\");\n}\n"]}
@@ -0,0 +1,23 @@
1
+ /** Append-only text storage that materializes immutable chunks only when a consumer requests a string. */
2
+ export declare class StreamingTextBuffer {
3
+ private chunks;
4
+ private cached;
5
+ private dirty;
6
+ private totalLength;
7
+ get length(): number;
8
+ append(chunk: string): void;
9
+ materialize(): string;
10
+ }
11
+ /**
12
+ * Projects growing structured text at geometrically spaced lengths and once at completion.
13
+ * Prefix work therefore remains bounded by a geometric series instead of running for every delta.
14
+ */
15
+ export declare class GeometricStreamingProjector<T> {
16
+ private readonly buffer;
17
+ private readonly project;
18
+ private nextProjectionLength;
19
+ constructor(project: (text: string) => T);
20
+ append(chunk: string): T | undefined;
21
+ finish(): T;
22
+ }
23
+ //# sourceMappingURL=streaming-content.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"streaming-content.d.ts","sourceRoot":"","sources":["../../src/utils/streaming-content.ts"],"names":[],"mappings":"AAAA,0GAA0G;AAC1G,qBAAa,mBAAmB;IAC/B,OAAO,CAAC,MAAM,CAAgB;IAC9B,OAAO,CAAC,MAAM,CAAM;IACpB,OAAO,CAAC,KAAK,CAAS;IACtB,OAAO,CAAC,WAAW,CAAK;IAExB,IAAI,MAAM,IAAI,MAAM,CAEnB;IAED,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAK1B;IAED,WAAW,IAAI,MAAM,CAMpB;CACD;AAED;;;GAGG;AACH,qBAAa,2BAA2B,CAAC,CAAC;IACzC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAA6B;IACpD,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAsB;IAC9C,OAAO,CAAC,oBAAoB,CAAK;IAEjC,YAAY,OAAO,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,CAAC,EAEvC;IAED,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,CAAC,GAAG,SAAS,CAQnC;IAED,MAAM,IAAI,CAAC,CAEV;CACD"}
@@ -0,0 +1,52 @@
1
+ /** Append-only text storage that materializes immutable chunks only when a consumer requests a string. */
2
+ export class StreamingTextBuffer {
3
+ constructor() {
4
+ this.chunks = [];
5
+ this.cached = "";
6
+ this.dirty = false;
7
+ this.totalLength = 0;
8
+ }
9
+ get length() {
10
+ return this.totalLength;
11
+ }
12
+ append(chunk) {
13
+ if (chunk.length === 0)
14
+ return;
15
+ this.chunks.push(chunk);
16
+ this.totalLength += chunk.length;
17
+ this.dirty = true;
18
+ }
19
+ materialize() {
20
+ if (!this.dirty)
21
+ return this.cached;
22
+ this.cached = this.chunks.join("");
23
+ this.chunks = [this.cached];
24
+ this.dirty = false;
25
+ return this.cached;
26
+ }
27
+ }
28
+ /**
29
+ * Projects growing structured text at geometrically spaced lengths and once at completion.
30
+ * Prefix work therefore remains bounded by a geometric series instead of running for every delta.
31
+ */
32
+ export class GeometricStreamingProjector {
33
+ constructor(project) {
34
+ this.buffer = new StreamingTextBuffer();
35
+ this.nextProjectionLength = 1;
36
+ this.project = project;
37
+ }
38
+ append(chunk) {
39
+ this.buffer.append(chunk);
40
+ if (this.buffer.length < this.nextProjectionLength)
41
+ return undefined;
42
+ const projected = this.project(this.buffer.materialize());
43
+ do {
44
+ this.nextProjectionLength *= 2;
45
+ } while (this.nextProjectionLength <= this.buffer.length);
46
+ return projected;
47
+ }
48
+ finish() {
49
+ return this.project(this.buffer.materialize());
50
+ }
51
+ }
52
+ //# sourceMappingURL=streaming-content.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"streaming-content.js","sourceRoot":"","sources":["../../src/utils/streaming-content.ts"],"names":[],"mappings":"AAAA,0GAA0G;AAC1G,MAAM,OAAO,mBAAmB;IAAhC;QACS,WAAM,GAAa,EAAE,CAAC;QACtB,WAAM,GAAG,EAAE,CAAC;QACZ,UAAK,GAAG,KAAK,CAAC;QACd,gBAAW,GAAG,CAAC,CAAC;IAoBzB,CAAC;IAlBA,IAAI,MAAM;QACT,OAAO,IAAI,CAAC,WAAW,CAAC;IACzB,CAAC;IAED,MAAM,CAAC,KAAa;QACnB,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO;QAC/B,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACxB,IAAI,CAAC,WAAW,IAAI,KAAK,CAAC,MAAM,CAAC;QACjC,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC;IACnB,CAAC;IAED,WAAW;QACV,IAAI,CAAC,IAAI,CAAC,KAAK;YAAE,OAAO,IAAI,CAAC,MAAM,CAAC;QACpC,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QACnC,IAAI,CAAC,MAAM,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QAC5B,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC;QACnB,OAAO,IAAI,CAAC,MAAM,CAAC;IACpB,CAAC;CACD;AAED;;;GAGG;AACH,MAAM,OAAO,2BAA2B;IAKvC,YAAY,OAA4B;QAJvB,WAAM,GAAG,IAAI,mBAAmB,EAAE,CAAC;QAE5C,yBAAoB,GAAG,CAAC,CAAC;QAGhC,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC;IACxB,CAAC;IAED,MAAM,CAAC,KAAa;QACnB,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;QAC1B,IAAI,IAAI,CAAC,MAAM,CAAC,MAAM,GAAG,IAAI,CAAC,oBAAoB;YAAE,OAAO,SAAS,CAAC;QACrE,MAAM,SAAS,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,WAAW,EAAE,CAAC,CAAC;QAC1D,GAAG,CAAC;YACH,IAAI,CAAC,oBAAoB,IAAI,CAAC,CAAC;QAChC,CAAC,QAAQ,IAAI,CAAC,oBAAoB,IAAI,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE;QAC1D,OAAO,SAAS,CAAC;IAClB,CAAC;IAED,MAAM;QACL,OAAO,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,WAAW,EAAE,CAAC,CAAC;IAChD,CAAC;CACD","sourcesContent":["/** Append-only text storage that materializes immutable chunks only when a consumer requests a string. */\nexport class StreamingTextBuffer {\n\tprivate chunks: string[] = [];\n\tprivate cached = \"\";\n\tprivate dirty = false;\n\tprivate totalLength = 0;\n\n\tget length(): number {\n\t\treturn this.totalLength;\n\t}\n\n\tappend(chunk: string): void {\n\t\tif (chunk.length === 0) return;\n\t\tthis.chunks.push(chunk);\n\t\tthis.totalLength += chunk.length;\n\t\tthis.dirty = true;\n\t}\n\n\tmaterialize(): string {\n\t\tif (!this.dirty) return this.cached;\n\t\tthis.cached = this.chunks.join(\"\");\n\t\tthis.chunks = [this.cached];\n\t\tthis.dirty = false;\n\t\treturn this.cached;\n\t}\n}\n\n/**\n * Projects growing structured text at geometrically spaced lengths and once at completion.\n * Prefix work therefore remains bounded by a geometric series instead of running for every delta.\n */\nexport class GeometricStreamingProjector<T> {\n\tprivate readonly buffer = new StreamingTextBuffer();\n\tprivate readonly project: (text: string) => T;\n\tprivate nextProjectionLength = 1;\n\n\tconstructor(project: (text: string) => T) {\n\t\tthis.project = project;\n\t}\n\n\tappend(chunk: string): T | undefined {\n\t\tthis.buffer.append(chunk);\n\t\tif (this.buffer.length < this.nextProjectionLength) return undefined;\n\t\tconst projected = this.project(this.buffer.materialize());\n\t\tdo {\n\t\t\tthis.nextProjectionLength *= 2;\n\t\t} while (this.nextProjectionLength <= this.buffer.length);\n\t\treturn projected;\n\t}\n\n\tfinish(): T {\n\t\treturn this.project(this.buffer.materialize());\n\t}\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"truncate.d.ts","sourceRoot":"","sources":["../../src/utils/truncate.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,eAAO,MAAM,iBAAiB,OAAO,CAAC;AACtC,eAAO,MAAM,iBAAiB,QAAY,CAAC;AAC3C,eAAO,MAAM,oBAAoB,MAAM,CAAC;AAExC,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;AAaD;;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,CAkF/F;AAED;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,GAAE,iBAAsB,GAAG,gBAAgB,CAyE/F;AAuBD;;;GAGG;AACH,wBAAgB,YAAY,CAC3B,IAAI,EAAE,MAAM,EACZ,QAAQ,GAAE,MAA6B,GACrC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,YAAY,EAAE,OAAO,CAAA;CAAE,CAKzC","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 (except bash tail truncation edge case).\n */\n\nexport const DEFAULT_MAX_LINES = 2000;\nexport const DEFAULT_MAX_BYTES = 50 * 1024; // 50KB\nexport const GREP_MAX_LINE_LENGTH = 500; // Max chars per grep match line\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\nfunction splitLinesForCounting(content: string): string[] {\n\tif (content.length === 0) {\n\t\treturn [];\n\t}\n\tconst lines = content.split(\"\\n\");\n\tif (content.endsWith(\"\\n\")) {\n\t\tlines.pop();\n\t}\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\tconst maxLines = options.maxLines ?? DEFAULT_MAX_LINES;\n\tconst maxBytes = options.maxBytes ?? DEFAULT_MAX_BYTES;\n\n\tconst totalBytes = Buffer.byteLength(content, \"utf-8\");\n\tconst lines = splitLinesForCounting(content);\n\tconst totalLines = lines.length;\n\n\t// Check if no truncation needed\n\tif (totalLines <= maxLines && totalBytes <= maxBytes) {\n\t\treturn {\n\t\t\tcontent,\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 = Buffer.byteLength(lines[0], \"utf-8\");\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 = Buffer.byteLength(line, \"utf-8\") + (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// If we exited due to line limit\n\tif (outputLinesArr.length >= maxLines && outputBytesCount <= maxBytes) {\n\t\ttruncatedBy = \"lines\";\n\t}\n\n\tconst outputContent = outputLinesArr.join(\"\\n\");\n\tconst finalOutputBytes = Buffer.byteLength(outputContent, \"utf-8\");\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\n/**\n * Truncate content from the tail (keep last N lines/bytes).\n * Suitable for bash output where you want to see the end (errors, final results).\n *\n * May return partial first line if the last line of original content exceeds byte limit.\n */\nexport function truncateTail(content: string, options: TruncationOptions = {}): TruncationResult {\n\tconst maxLines = options.maxLines ?? DEFAULT_MAX_LINES;\n\tconst maxBytes = options.maxBytes ?? DEFAULT_MAX_BYTES;\n\n\tconst totalBytes = Buffer.byteLength(content, \"utf-8\");\n\tconst lines = splitLinesForCounting(content);\n\tconst totalLines = lines.length;\n\n\t// Check if no truncation needed\n\tif (totalLines <= maxLines && totalBytes <= maxBytes) {\n\t\treturn {\n\t\t\tcontent,\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// Work backwards from the end\n\tconst outputLinesArr: string[] = [];\n\tlet outputBytesCount = 0;\n\tlet truncatedBy: \"lines\" | \"bytes\" = \"lines\";\n\tlet lastLinePartial = false;\n\n\tfor (let i = lines.length - 1; i >= 0 && outputLinesArr.length < maxLines; i--) {\n\t\tconst line = lines[i];\n\t\tconst lineBytes = Buffer.byteLength(line, \"utf-8\") + (outputLinesArr.length > 0 ? 1 : 0); // +1 for newline\n\n\t\tif (outputBytesCount + lineBytes > maxBytes) {\n\t\t\ttruncatedBy = \"bytes\";\n\t\t\t// Edge case: if we haven't added ANY lines yet and this line exceeds maxBytes,\n\t\t\t// take the end of the line (partial)\n\t\t\tif (outputLinesArr.length === 0) {\n\t\t\t\tconst truncatedLine = truncateStringToBytesFromEnd(line, maxBytes);\n\t\t\t\toutputLinesArr.unshift(truncatedLine);\n\t\t\t\toutputBytesCount = Buffer.byteLength(truncatedLine, \"utf-8\");\n\t\t\t\tlastLinePartial = true;\n\t\t\t}\n\t\t\tbreak;\n\t\t}\n\n\t\toutputLinesArr.unshift(line);\n\t\toutputBytesCount += lineBytes;\n\t}\n\n\t// If we exited due to line limit\n\tif (outputLinesArr.length >= maxLines && outputBytesCount <= maxBytes) {\n\t\ttruncatedBy = \"lines\";\n\t}\n\n\tconst outputContent = outputLinesArr.join(\"\\n\");\n\tconst finalOutputBytes = Buffer.byteLength(outputContent, \"utf-8\");\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,\n\t\tfirstLineExceedsLimit: false,\n\t\tmaxLines,\n\t\tmaxBytes,\n\t};\n}\n\n/**\n * Truncate a string to fit within a byte limit (from the end).\n * Handles multi-byte UTF-8 characters correctly.\n */\nfunction truncateStringToBytesFromEnd(str: string, maxBytes: number): string {\n\tconst buf = Buffer.from(str, \"utf-8\");\n\tif (buf.length <= maxBytes) {\n\t\treturn str;\n\t}\n\n\t// Start from the end, skip maxBytes back\n\tlet start = buf.length - maxBytes;\n\n\t// Find a valid UTF-8 boundary (start of a character)\n\twhile (start < buf.length && (buf[start] & 0xc0) === 0x80) {\n\t\tstart++;\n\t}\n\n\treturn buf.slice(start).toString(\"utf-8\");\n}\n\n/**\n * Truncate a single line to max characters, adding [truncated] suffix.\n * Used for grep match lines.\n */\nexport function truncateLine(\n\tline: string,\n\tmaxChars: number = GREP_MAX_LINE_LENGTH,\n): { text: string; wasTruncated: boolean } {\n\tif (line.length <= maxChars) {\n\t\treturn { text: line, wasTruncated: false };\n\t}\n\treturn { text: `${line.slice(0, maxChars)}... [truncated]`, wasTruncated: true };\n}\n"]}
1
+ {"version":3,"file":"truncate.d.ts","sourceRoot":"","sources":["../../src/utils/truncate.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,eAAO,MAAM,iBAAiB,OAAO,CAAC;AACtC,eAAO,MAAM,iBAAiB,QAAY,CAAC;AAC3C,eAAO,MAAM,oBAAoB,MAAM,CAAC;AAExC,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;AA+ED;;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,CA2C/F;AAED;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,GAAE,iBAAsB,GAAG,gBAAgB,CAwC/F;AAuBD;;;GAGG;AACH,wBAAgB,YAAY,CAC3B,IAAI,EAAE,MAAM,EACZ,QAAQ,GAAE,MAA6B,GACrC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,YAAY,EAAE,OAAO,CAAA;CAAE,CAKzC"}
@@ -20,6 +20,47 @@ function splitLinesForCounting(content) {
20
20
  }
21
21
  return lines;
22
22
  }
23
+ function measureTruncation(content, options) {
24
+ const lines = splitLinesForCounting(content);
25
+ return {
26
+ lines,
27
+ maxLines: options.maxLines ?? DEFAULT_MAX_LINES,
28
+ maxBytes: options.maxBytes ?? DEFAULT_MAX_BYTES,
29
+ totalLines: lines.length,
30
+ totalBytes: Buffer.byteLength(content, "utf-8"),
31
+ };
32
+ }
33
+ function buildTruncationResult(measurement, outcome) {
34
+ return {
35
+ ...outcome,
36
+ totalLines: measurement.totalLines,
37
+ totalBytes: measurement.totalBytes,
38
+ outputBytes: Buffer.byteLength(outcome.content, "utf-8"),
39
+ lastLinePartial: outcome.lastLinePartial ?? false,
40
+ firstLineExceedsLimit: outcome.firstLineExceedsLimit ?? false,
41
+ maxLines: measurement.maxLines,
42
+ maxBytes: measurement.maxBytes,
43
+ };
44
+ }
45
+ function unchangedTruncationResult(content, measurement) {
46
+ if (measurement.totalLines > measurement.maxLines || measurement.totalBytes > measurement.maxBytes)
47
+ return undefined;
48
+ return buildTruncationResult(measurement, {
49
+ content,
50
+ truncated: false,
51
+ truncatedBy: null,
52
+ outputLines: measurement.totalLines,
53
+ });
54
+ }
55
+ function completedTruncationResult(measurement, outputLines, truncatedBy, lastLinePartial = false) {
56
+ return buildTruncationResult(measurement, {
57
+ content: outputLines.join("\n"),
58
+ truncated: true,
59
+ truncatedBy,
60
+ outputLines: outputLines.length,
61
+ lastLinePartial,
62
+ });
63
+ }
23
64
  /**
24
65
  * Format bytes as human-readable size.
25
66
  */
@@ -42,43 +83,21 @@ export function formatSize(bytes) {
42
83
  * returns empty content with firstLineExceedsLimit=true.
43
84
  */
44
85
  export function truncateHead(content, options = {}) {
45
- const maxLines = options.maxLines ?? DEFAULT_MAX_LINES;
46
- const maxBytes = options.maxBytes ?? DEFAULT_MAX_BYTES;
47
- const totalBytes = Buffer.byteLength(content, "utf-8");
48
- const lines = splitLinesForCounting(content);
49
- const totalLines = lines.length;
50
- // Check if no truncation needed
51
- if (totalLines <= maxLines && totalBytes <= maxBytes) {
52
- return {
53
- content,
54
- truncated: false,
55
- truncatedBy: null,
56
- totalLines,
57
- totalBytes,
58
- outputLines: totalLines,
59
- outputBytes: totalBytes,
60
- lastLinePartial: false,
61
- firstLineExceedsLimit: false,
62
- maxLines,
63
- maxBytes,
64
- };
65
- }
86
+ const measurement = measureTruncation(content, options);
87
+ const { lines, maxBytes, maxLines } = measurement;
88
+ const unchanged = unchangedTruncationResult(content, measurement);
89
+ if (unchanged)
90
+ return unchanged;
66
91
  // Check if first line alone exceeds byte limit
67
92
  const firstLineBytes = Buffer.byteLength(lines[0], "utf-8");
68
93
  if (firstLineBytes > maxBytes) {
69
- return {
94
+ return buildTruncationResult(measurement, {
70
95
  content: "",
71
96
  truncated: true,
72
97
  truncatedBy: "bytes",
73
- totalLines,
74
- totalBytes,
75
98
  outputLines: 0,
76
- outputBytes: 0,
77
- lastLinePartial: false,
78
99
  firstLineExceedsLimit: true,
79
- maxLines,
80
- maxBytes,
81
- };
100
+ });
82
101
  }
83
102
  // Collect complete lines that fit
84
103
  const outputLinesArr = [];
@@ -98,21 +117,7 @@ export function truncateHead(content, options = {}) {
98
117
  if (outputLinesArr.length >= maxLines && outputBytesCount <= maxBytes) {
99
118
  truncatedBy = "lines";
100
119
  }
101
- const outputContent = outputLinesArr.join("\n");
102
- const finalOutputBytes = Buffer.byteLength(outputContent, "utf-8");
103
- return {
104
- content: outputContent,
105
- truncated: true,
106
- truncatedBy,
107
- totalLines,
108
- totalBytes,
109
- outputLines: outputLinesArr.length,
110
- outputBytes: finalOutputBytes,
111
- lastLinePartial: false,
112
- firstLineExceedsLimit: false,
113
- maxLines,
114
- maxBytes,
115
- };
120
+ return completedTruncationResult(measurement, outputLinesArr, truncatedBy);
116
121
  }
117
122
  /**
118
123
  * Truncate content from the tail (keep last N lines/bytes).
@@ -121,27 +126,11 @@ export function truncateHead(content, options = {}) {
121
126
  * May return partial first line if the last line of original content exceeds byte limit.
122
127
  */
123
128
  export function truncateTail(content, options = {}) {
124
- const maxLines = options.maxLines ?? DEFAULT_MAX_LINES;
125
- const maxBytes = options.maxBytes ?? DEFAULT_MAX_BYTES;
126
- const totalBytes = Buffer.byteLength(content, "utf-8");
127
- const lines = splitLinesForCounting(content);
128
- const totalLines = lines.length;
129
- // Check if no truncation needed
130
- if (totalLines <= maxLines && totalBytes <= maxBytes) {
131
- return {
132
- content,
133
- truncated: false,
134
- truncatedBy: null,
135
- totalLines,
136
- totalBytes,
137
- outputLines: totalLines,
138
- outputBytes: totalBytes,
139
- lastLinePartial: false,
140
- firstLineExceedsLimit: false,
141
- maxLines,
142
- maxBytes,
143
- };
144
- }
129
+ const measurement = measureTruncation(content, options);
130
+ const { lines, maxBytes, maxLines } = measurement;
131
+ const unchanged = unchangedTruncationResult(content, measurement);
132
+ if (unchanged)
133
+ return unchanged;
145
134
  // Work backwards from the end
146
135
  const outputLinesArr = [];
147
136
  let outputBytesCount = 0;
@@ -169,21 +158,7 @@ export function truncateTail(content, options = {}) {
169
158
  if (outputLinesArr.length >= maxLines && outputBytesCount <= maxBytes) {
170
159
  truncatedBy = "lines";
171
160
  }
172
- const outputContent = outputLinesArr.join("\n");
173
- const finalOutputBytes = Buffer.byteLength(outputContent, "utf-8");
174
- return {
175
- content: outputContent,
176
- truncated: true,
177
- truncatedBy,
178
- totalLines,
179
- totalBytes,
180
- outputLines: outputLinesArr.length,
181
- outputBytes: finalOutputBytes,
182
- lastLinePartial,
183
- firstLineExceedsLimit: false,
184
- maxLines,
185
- maxBytes,
186
- };
161
+ return completedTruncationResult(measurement, outputLinesArr, truncatedBy, lastLinePartial);
187
162
  }
188
163
  /**
189
164
  * Truncate a string to fit within a byte limit (from the end).
@@ -1 +1 @@
1
- {"version":3,"file":"truncate.js","sourceRoot":"","sources":["../../src/utils/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 (except bash tail truncation edge case).\n */\n\nexport const DEFAULT_MAX_LINES = 2000;\nexport const DEFAULT_MAX_BYTES = 50 * 1024; // 50KB\nexport const GREP_MAX_LINE_LENGTH = 500; // Max chars per grep match line\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\nfunction splitLinesForCounting(content: string): string[] {\n\tif (content.length === 0) {\n\t\treturn [];\n\t}\n\tconst lines = content.split(\"\\n\");\n\tif (content.endsWith(\"\\n\")) {\n\t\tlines.pop();\n\t}\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\tconst maxLines = options.maxLines ?? DEFAULT_MAX_LINES;\n\tconst maxBytes = options.maxBytes ?? DEFAULT_MAX_BYTES;\n\n\tconst totalBytes = Buffer.byteLength(content, \"utf-8\");\n\tconst lines = splitLinesForCounting(content);\n\tconst totalLines = lines.length;\n\n\t// Check if no truncation needed\n\tif (totalLines <= maxLines && totalBytes <= maxBytes) {\n\t\treturn {\n\t\t\tcontent,\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 = Buffer.byteLength(lines[0], \"utf-8\");\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 = Buffer.byteLength(line, \"utf-8\") + (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// If we exited due to line limit\n\tif (outputLinesArr.length >= maxLines && outputBytesCount <= maxBytes) {\n\t\ttruncatedBy = \"lines\";\n\t}\n\n\tconst outputContent = outputLinesArr.join(\"\\n\");\n\tconst finalOutputBytes = Buffer.byteLength(outputContent, \"utf-8\");\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\n/**\n * Truncate content from the tail (keep last N lines/bytes).\n * Suitable for bash output where you want to see the end (errors, final results).\n *\n * May return partial first line if the last line of original content exceeds byte limit.\n */\nexport function truncateTail(content: string, options: TruncationOptions = {}): TruncationResult {\n\tconst maxLines = options.maxLines ?? DEFAULT_MAX_LINES;\n\tconst maxBytes = options.maxBytes ?? DEFAULT_MAX_BYTES;\n\n\tconst totalBytes = Buffer.byteLength(content, \"utf-8\");\n\tconst lines = splitLinesForCounting(content);\n\tconst totalLines = lines.length;\n\n\t// Check if no truncation needed\n\tif (totalLines <= maxLines && totalBytes <= maxBytes) {\n\t\treturn {\n\t\t\tcontent,\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// Work backwards from the end\n\tconst outputLinesArr: string[] = [];\n\tlet outputBytesCount = 0;\n\tlet truncatedBy: \"lines\" | \"bytes\" = \"lines\";\n\tlet lastLinePartial = false;\n\n\tfor (let i = lines.length - 1; i >= 0 && outputLinesArr.length < maxLines; i--) {\n\t\tconst line = lines[i];\n\t\tconst lineBytes = Buffer.byteLength(line, \"utf-8\") + (outputLinesArr.length > 0 ? 1 : 0); // +1 for newline\n\n\t\tif (outputBytesCount + lineBytes > maxBytes) {\n\t\t\ttruncatedBy = \"bytes\";\n\t\t\t// Edge case: if we haven't added ANY lines yet and this line exceeds maxBytes,\n\t\t\t// take the end of the line (partial)\n\t\t\tif (outputLinesArr.length === 0) {\n\t\t\t\tconst truncatedLine = truncateStringToBytesFromEnd(line, maxBytes);\n\t\t\t\toutputLinesArr.unshift(truncatedLine);\n\t\t\t\toutputBytesCount = Buffer.byteLength(truncatedLine, \"utf-8\");\n\t\t\t\tlastLinePartial = true;\n\t\t\t}\n\t\t\tbreak;\n\t\t}\n\n\t\toutputLinesArr.unshift(line);\n\t\toutputBytesCount += lineBytes;\n\t}\n\n\t// If we exited due to line limit\n\tif (outputLinesArr.length >= maxLines && outputBytesCount <= maxBytes) {\n\t\ttruncatedBy = \"lines\";\n\t}\n\n\tconst outputContent = outputLinesArr.join(\"\\n\");\n\tconst finalOutputBytes = Buffer.byteLength(outputContent, \"utf-8\");\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,\n\t\tfirstLineExceedsLimit: false,\n\t\tmaxLines,\n\t\tmaxBytes,\n\t};\n}\n\n/**\n * Truncate a string to fit within a byte limit (from the end).\n * Handles multi-byte UTF-8 characters correctly.\n */\nfunction truncateStringToBytesFromEnd(str: string, maxBytes: number): string {\n\tconst buf = Buffer.from(str, \"utf-8\");\n\tif (buf.length <= maxBytes) {\n\t\treturn str;\n\t}\n\n\t// Start from the end, skip maxBytes back\n\tlet start = buf.length - maxBytes;\n\n\t// Find a valid UTF-8 boundary (start of a character)\n\twhile (start < buf.length && (buf[start] & 0xc0) === 0x80) {\n\t\tstart++;\n\t}\n\n\treturn buf.slice(start).toString(\"utf-8\");\n}\n\n/**\n * Truncate a single line to max characters, adding [truncated] suffix.\n * Used for grep match lines.\n */\nexport function truncateLine(\n\tline: string,\n\tmaxChars: number = GREP_MAX_LINE_LENGTH,\n): { text: string; wasTruncated: boolean } {\n\tif (line.length <= maxChars) {\n\t\treturn { text: line, wasTruncated: false };\n\t}\n\treturn { text: `${line.slice(0, maxChars)}... [truncated]`, wasTruncated: true };\n}\n"]}
1
+ {"version":3,"file":"truncate.js","sourceRoot":"","sources":["../../src/utils/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 (except bash tail truncation edge case).\n */\n\nexport const DEFAULT_MAX_LINES = 2000;\nexport const DEFAULT_MAX_BYTES = 50 * 1024; // 50KB\nexport const GREP_MAX_LINE_LENGTH = 500; // Max chars per grep match line\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\nfunction splitLinesForCounting(content: string): string[] {\n\tif (content.length === 0) {\n\t\treturn [];\n\t}\n\tconst lines = content.split(\"\\n\");\n\tif (content.endsWith(\"\\n\")) {\n\t\tlines.pop();\n\t}\n\treturn lines;\n}\n\ninterface TruncationMeasurement {\n\tlines: string[];\n\tmaxLines: number;\n\tmaxBytes: number;\n\ttotalLines: number;\n\ttotalBytes: number;\n}\n\ninterface TruncationOutcome {\n\tcontent: string;\n\ttruncated: boolean;\n\ttruncatedBy: \"lines\" | \"bytes\" | null;\n\toutputLines: number;\n\tlastLinePartial?: boolean;\n\tfirstLineExceedsLimit?: boolean;\n}\n\nfunction measureTruncation(content: string, options: TruncationOptions): TruncationMeasurement {\n\tconst lines = splitLinesForCounting(content);\n\treturn {\n\t\tlines,\n\t\tmaxLines: options.maxLines ?? DEFAULT_MAX_LINES,\n\t\tmaxBytes: options.maxBytes ?? DEFAULT_MAX_BYTES,\n\t\ttotalLines: lines.length,\n\t\ttotalBytes: Buffer.byteLength(content, \"utf-8\"),\n\t};\n}\n\nfunction buildTruncationResult(measurement: TruncationMeasurement, outcome: TruncationOutcome): TruncationResult {\n\treturn {\n\t\t...outcome,\n\t\ttotalLines: measurement.totalLines,\n\t\ttotalBytes: measurement.totalBytes,\n\t\toutputBytes: Buffer.byteLength(outcome.content, \"utf-8\"),\n\t\tlastLinePartial: outcome.lastLinePartial ?? false,\n\t\tfirstLineExceedsLimit: outcome.firstLineExceedsLimit ?? false,\n\t\tmaxLines: measurement.maxLines,\n\t\tmaxBytes: measurement.maxBytes,\n\t};\n}\n\nfunction unchangedTruncationResult(content: string, measurement: TruncationMeasurement): TruncationResult | undefined {\n\tif (measurement.totalLines > measurement.maxLines || measurement.totalBytes > measurement.maxBytes) return undefined;\n\treturn buildTruncationResult(measurement, {\n\t\tcontent,\n\t\ttruncated: false,\n\t\ttruncatedBy: null,\n\t\toutputLines: measurement.totalLines,\n\t});\n}\n\nfunction completedTruncationResult(\n\tmeasurement: TruncationMeasurement,\n\toutputLines: string[],\n\ttruncatedBy: \"lines\" | \"bytes\",\n\tlastLinePartial = false,\n): TruncationResult {\n\treturn buildTruncationResult(measurement, {\n\t\tcontent: outputLines.join(\"\\n\"),\n\t\ttruncated: true,\n\t\ttruncatedBy,\n\t\toutputLines: outputLines.length,\n\t\tlastLinePartial,\n\t});\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\tconst measurement = measureTruncation(content, options);\n\tconst { lines, maxBytes, maxLines } = measurement;\n\n\tconst unchanged = unchangedTruncationResult(content, measurement);\n\tif (unchanged) return unchanged;\n\n\t// Check if first line alone exceeds byte limit\n\tconst firstLineBytes = Buffer.byteLength(lines[0], \"utf-8\");\n\tif (firstLineBytes > maxBytes) {\n\t\treturn buildTruncationResult(measurement, {\n\t\t\tcontent: \"\",\n\t\t\ttruncated: true,\n\t\t\ttruncatedBy: \"bytes\",\n\t\t\toutputLines: 0,\n\t\t\tfirstLineExceedsLimit: true,\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 = Buffer.byteLength(line, \"utf-8\") + (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// If we exited due to line limit\n\tif (outputLinesArr.length >= maxLines && outputBytesCount <= maxBytes) {\n\t\ttruncatedBy = \"lines\";\n\t}\n\n\treturn completedTruncationResult(measurement, outputLinesArr, truncatedBy);\n}\n\n/**\n * Truncate content from the tail (keep last N lines/bytes).\n * Suitable for bash output where you want to see the end (errors, final results).\n *\n * May return partial first line if the last line of original content exceeds byte limit.\n */\nexport function truncateTail(content: string, options: TruncationOptions = {}): TruncationResult {\n\tconst measurement = measureTruncation(content, options);\n\tconst { lines, maxBytes, maxLines } = measurement;\n\n\tconst unchanged = unchangedTruncationResult(content, measurement);\n\tif (unchanged) return unchanged;\n\n\t// Work backwards from the end\n\tconst outputLinesArr: string[] = [];\n\tlet outputBytesCount = 0;\n\tlet truncatedBy: \"lines\" | \"bytes\" = \"lines\";\n\tlet lastLinePartial = false;\n\n\tfor (let i = lines.length - 1; i >= 0 && outputLinesArr.length < maxLines; i--) {\n\t\tconst line = lines[i];\n\t\tconst lineBytes = Buffer.byteLength(line, \"utf-8\") + (outputLinesArr.length > 0 ? 1 : 0); // +1 for newline\n\n\t\tif (outputBytesCount + lineBytes > maxBytes) {\n\t\t\ttruncatedBy = \"bytes\";\n\t\t\t// Edge case: if we haven't added ANY lines yet and this line exceeds maxBytes,\n\t\t\t// take the end of the line (partial)\n\t\t\tif (outputLinesArr.length === 0) {\n\t\t\t\tconst truncatedLine = truncateStringToBytesFromEnd(line, maxBytes);\n\t\t\t\toutputLinesArr.unshift(truncatedLine);\n\t\t\t\toutputBytesCount = Buffer.byteLength(truncatedLine, \"utf-8\");\n\t\t\t\tlastLinePartial = true;\n\t\t\t}\n\t\t\tbreak;\n\t\t}\n\n\t\toutputLinesArr.unshift(line);\n\t\toutputBytesCount += lineBytes;\n\t}\n\n\t// If we exited due to line limit\n\tif (outputLinesArr.length >= maxLines && outputBytesCount <= maxBytes) {\n\t\ttruncatedBy = \"lines\";\n\t}\n\n\treturn completedTruncationResult(measurement, outputLinesArr, truncatedBy, lastLinePartial);\n}\n\n/**\n * Truncate a string to fit within a byte limit (from the end).\n * Handles multi-byte UTF-8 characters correctly.\n */\nfunction truncateStringToBytesFromEnd(str: string, maxBytes: number): string {\n\tconst buf = Buffer.from(str, \"utf-8\");\n\tif (buf.length <= maxBytes) {\n\t\treturn str;\n\t}\n\n\t// Start from the end, skip maxBytes back\n\tlet start = buf.length - maxBytes;\n\n\t// Find a valid UTF-8 boundary (start of a character)\n\twhile (start < buf.length && (buf[start] & 0xc0) === 0x80) {\n\t\tstart++;\n\t}\n\n\treturn buf.slice(start).toString(\"utf-8\");\n}\n\n/**\n * Truncate a single line to max characters, adding [truncated] suffix.\n * Used for grep match lines.\n */\nexport function truncateLine(\n\tline: string,\n\tmaxChars: number = GREP_MAX_LINE_LENGTH,\n): { text: string; wasTruncated: boolean } {\n\tif (line.length <= maxChars) {\n\t\treturn { text: line, wasTruncated: false };\n\t}\n\treturn { text: `${line.slice(0, maxChars)}... [truncated]`, wasTruncated: true };\n}\n"]}
package/dist/uuid.d.ts CHANGED
@@ -1,2 +1,2 @@
1
- export { uuidv7 } from "@caupulican/pi-ai";
1
+ export { uuidv7 } from "@caupulican/pi-ai/uuid";
2
2
  //# sourceMappingURL=uuid.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"uuid.d.ts","sourceRoot":"","sources":["../src/uuid.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC","sourcesContent":["export { uuidv7 } from \"@caupulican/pi-ai\";\n"]}
1
+ {"version":3,"file":"uuid.d.ts","sourceRoot":"","sources":["../src/uuid.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,wBAAwB,CAAC"}
package/dist/uuid.js CHANGED
@@ -1,2 +1,2 @@
1
- export { uuidv7 } from "@caupulican/pi-ai";
1
+ export { uuidv7 } from "@caupulican/pi-ai/uuid";
2
2
  //# sourceMappingURL=uuid.js.map
package/dist/uuid.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"uuid.js","sourceRoot":"","sources":["../src/uuid.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC","sourcesContent":["export { uuidv7 } from \"@caupulican/pi-ai\";\n"]}
1
+ {"version":3,"file":"uuid.js","sourceRoot":"","sources":["../src/uuid.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,wBAAwB,CAAC","sourcesContent":["export { uuidv7 } from \"@caupulican/pi-ai/uuid\";\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@caupulican/pi-agent-core",
3
- "version": "0.81.42",
3
+ "version": "0.84.1",
4
4
  "description": "General-purpose agent with transport abstraction, state management, and attachment support",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -26,14 +26,14 @@
26
26
  ],
27
27
  "scripts": {
28
28
  "clean": "shx rm -rf dist",
29
- "build": "node ../../scripts/tsgo-or-tsc.mjs -p tsconfig.build.json",
29
+ "build": "node ../../scripts/run-tsc.mjs -p tsconfig.build.json",
30
30
  "test": "vitest --run",
31
31
  "test:harness": "vitest --run --config vitest.harness.config.ts",
32
32
  "coverage:harness": "vitest --run --config vitest.harness.config.ts --coverage",
33
33
  "prepublishOnly": "npm run clean && npm run build"
34
34
  },
35
35
  "dependencies": {
36
- "@caupulican/pi-ai": "^0.81.42",
36
+ "@caupulican/pi-ai": "^0.84.1",
37
37
  "ignore": "7.0.5",
38
38
  "typebox": "1.1.38",
39
39
  "yaml": "2.9.0"
@@ -53,12 +53,12 @@
53
53
  "directory": "packages/agent"
54
54
  },
55
55
  "engines": {
56
- "node": ">=22.19.0"
56
+ "node": ">=24.18.0"
57
57
  },
58
58
  "devDependencies": {
59
59
  "@types/node": "24.12.4",
60
- "@vitest/coverage-v8": "3.2.4",
61
- "typescript": "5.9.3",
62
- "vitest": "3.2.4"
60
+ "@vitest/coverage-v8": "3.2.7",
61
+ "typescript": "7.0.2",
62
+ "vitest": "3.2.7"
63
63
  }
64
64
  }
@@ -1,20 +0,0 @@
1
- /** Incremental line decoder that joins a fragmented line only once, when it completes. */
2
- export declare class StreamingLineDecoder {
3
- private readonly maxLineChars;
4
- private readonly overflow;
5
- private readonly lineEndings;
6
- private parts;
7
- private length;
8
- private skipLeadingLf;
9
- private skippingOversizedLine;
10
- constructor(maxLineChars: number, options?: {
11
- overflow?: "throw" | "skip";
12
- lineEndings?: "lf" | "any";
13
- });
14
- push(text: string): string[];
15
- finish(): string | undefined;
16
- private append;
17
- private takeLine;
18
- private resetLine;
19
- }
20
- //# sourceMappingURL=streaming-lines.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"streaming-lines.d.ts","sourceRoot":"","sources":["../../src/utils/streaming-lines.ts"],"names":[],"mappings":"AAAA,0FAA0F;AAC1F,qBAAa,oBAAoB;IAChC,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAS;IACtC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAmB;IAC5C,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAe;IAC3C,OAAO,CAAC,KAAK,CAAgB;IAC7B,OAAO,CAAC,MAAM,CAAK;IACnB,OAAO,CAAC,aAAa,CAAS;IAC9B,OAAO,CAAC,qBAAqB,CAAS;IAEtC,YAAY,YAAY,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE;QAAE,QAAQ,CAAC,EAAE,OAAO,GAAG,MAAM,CAAC;QAAC,WAAW,CAAC,EAAE,IAAI,GAAG,KAAK,CAAA;KAAE,EAItG;IAED,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,CAqB3B;IAED,MAAM,IAAI,MAAM,GAAG,SAAS,CAQ3B;IAED,OAAO,CAAC,MAAM;IAed,OAAO,CAAC,QAAQ;IAMhB,OAAO,CAAC,SAAS;CAKjB","sourcesContent":["/** Incremental line decoder that joins a fragmented line only once, when it completes. */\nexport class StreamingLineDecoder {\n\tprivate readonly maxLineChars: number;\n\tprivate readonly overflow: \"throw\" | \"skip\";\n\tprivate readonly lineEndings: \"lf\" | \"any\";\n\tprivate parts: string[] = [];\n\tprivate length = 0;\n\tprivate skipLeadingLf = false;\n\tprivate skippingOversizedLine = false;\n\n\tconstructor(maxLineChars: number, options?: { overflow?: \"throw\" | \"skip\"; lineEndings?: \"lf\" | \"any\" }) {\n\t\tthis.maxLineChars = maxLineChars;\n\t\tthis.overflow = options?.overflow ?? \"throw\";\n\t\tthis.lineEndings = options?.lineEndings ?? \"any\";\n\t}\n\n\tpush(text: string): string[] {\n\t\tconst lines: string[] = [];\n\t\tlet start = 0;\n\t\tif (this.skipLeadingLf) {\n\t\t\tthis.skipLeadingLf = false;\n\t\t\tif (text.startsWith(\"\\n\")) start = 1;\n\t\t}\n\t\tfor (let index = start; index < text.length; index++) {\n\t\t\tconst char = text[index];\n\t\t\tif (char !== \"\\n\" && (this.lineEndings === \"lf\" || char !== \"\\r\")) continue;\n\t\t\tthis.append(text.slice(start, index));\n\t\t\tif (this.skippingOversizedLine) this.resetLine();\n\t\t\telse lines.push(this.takeLine());\n\t\t\tif (char === \"\\r\") {\n\t\t\t\tif (text[index + 1] === \"\\n\") index++;\n\t\t\t\telse if (index + 1 === text.length) this.skipLeadingLf = true;\n\t\t\t}\n\t\t\tstart = index + 1;\n\t\t}\n\t\tthis.append(text.slice(start));\n\t\treturn lines;\n\t}\n\n\tfinish(): string | undefined {\n\t\tthis.skipLeadingLf = false;\n\t\tif (this.skippingOversizedLine) {\n\t\t\tthis.resetLine();\n\t\t\treturn undefined;\n\t\t}\n\t\tif (this.length === 0 && this.parts.length === 0) return undefined;\n\t\treturn this.takeLine();\n\t}\n\n\tprivate append(part: string): void {\n\t\tif (part.length === 0 || this.skippingOversizedLine) return;\n\t\tthis.length += part.length;\n\t\tif (this.length > this.maxLineChars) {\n\t\t\tif (this.overflow === \"throw\") {\n\t\t\t\tthrow new Error(`Stream exceeded the ${this.maxLineChars} character line limit`);\n\t\t\t}\n\t\t\tthis.parts = [];\n\t\t\tthis.length = 0;\n\t\t\tthis.skippingOversizedLine = true;\n\t\t\treturn;\n\t\t}\n\t\tthis.parts.push(part);\n\t}\n\n\tprivate takeLine(): string {\n\t\tconst line = this.parts.length === 0 ? \"\" : this.parts.length === 1 ? this.parts[0] : this.parts.join(\"\");\n\t\tthis.resetLine();\n\t\treturn line;\n\t}\n\n\tprivate resetLine(): void {\n\t\tthis.parts = [];\n\t\tthis.length = 0;\n\t\tthis.skippingOversizedLine = false;\n\t}\n}\n"]}
@@ -1,79 +0,0 @@
1
- /** Incremental line decoder that joins a fragmented line only once, when it completes. */
2
- export class StreamingLineDecoder {
3
- maxLineChars;
4
- overflow;
5
- lineEndings;
6
- parts = [];
7
- length = 0;
8
- skipLeadingLf = false;
9
- skippingOversizedLine = false;
10
- constructor(maxLineChars, options) {
11
- this.maxLineChars = maxLineChars;
12
- this.overflow = options?.overflow ?? "throw";
13
- this.lineEndings = options?.lineEndings ?? "any";
14
- }
15
- push(text) {
16
- const lines = [];
17
- let start = 0;
18
- if (this.skipLeadingLf) {
19
- this.skipLeadingLf = false;
20
- if (text.startsWith("\n"))
21
- start = 1;
22
- }
23
- for (let index = start; index < text.length; index++) {
24
- const char = text[index];
25
- if (char !== "\n" && (this.lineEndings === "lf" || char !== "\r"))
26
- continue;
27
- this.append(text.slice(start, index));
28
- if (this.skippingOversizedLine)
29
- this.resetLine();
30
- else
31
- lines.push(this.takeLine());
32
- if (char === "\r") {
33
- if (text[index + 1] === "\n")
34
- index++;
35
- else if (index + 1 === text.length)
36
- this.skipLeadingLf = true;
37
- }
38
- start = index + 1;
39
- }
40
- this.append(text.slice(start));
41
- return lines;
42
- }
43
- finish() {
44
- this.skipLeadingLf = false;
45
- if (this.skippingOversizedLine) {
46
- this.resetLine();
47
- return undefined;
48
- }
49
- if (this.length === 0 && this.parts.length === 0)
50
- return undefined;
51
- return this.takeLine();
52
- }
53
- append(part) {
54
- if (part.length === 0 || this.skippingOversizedLine)
55
- return;
56
- this.length += part.length;
57
- if (this.length > this.maxLineChars) {
58
- if (this.overflow === "throw") {
59
- throw new Error(`Stream exceeded the ${this.maxLineChars} character line limit`);
60
- }
61
- this.parts = [];
62
- this.length = 0;
63
- this.skippingOversizedLine = true;
64
- return;
65
- }
66
- this.parts.push(part);
67
- }
68
- takeLine() {
69
- const line = this.parts.length === 0 ? "" : this.parts.length === 1 ? this.parts[0] : this.parts.join("");
70
- this.resetLine();
71
- return line;
72
- }
73
- resetLine() {
74
- this.parts = [];
75
- this.length = 0;
76
- this.skippingOversizedLine = false;
77
- }
78
- }
79
- //# sourceMappingURL=streaming-lines.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"streaming-lines.js","sourceRoot":"","sources":["../../src/utils/streaming-lines.ts"],"names":[],"mappings":"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","sourcesContent":["/** Incremental line decoder that joins a fragmented line only once, when it completes. */\nexport class StreamingLineDecoder {\n\tprivate readonly maxLineChars: number;\n\tprivate readonly overflow: \"throw\" | \"skip\";\n\tprivate readonly lineEndings: \"lf\" | \"any\";\n\tprivate parts: string[] = [];\n\tprivate length = 0;\n\tprivate skipLeadingLf = false;\n\tprivate skippingOversizedLine = false;\n\n\tconstructor(maxLineChars: number, options?: { overflow?: \"throw\" | \"skip\"; lineEndings?: \"lf\" | \"any\" }) {\n\t\tthis.maxLineChars = maxLineChars;\n\t\tthis.overflow = options?.overflow ?? \"throw\";\n\t\tthis.lineEndings = options?.lineEndings ?? \"any\";\n\t}\n\n\tpush(text: string): string[] {\n\t\tconst lines: string[] = [];\n\t\tlet start = 0;\n\t\tif (this.skipLeadingLf) {\n\t\t\tthis.skipLeadingLf = false;\n\t\t\tif (text.startsWith(\"\\n\")) start = 1;\n\t\t}\n\t\tfor (let index = start; index < text.length; index++) {\n\t\t\tconst char = text[index];\n\t\t\tif (char !== \"\\n\" && (this.lineEndings === \"lf\" || char !== \"\\r\")) continue;\n\t\t\tthis.append(text.slice(start, index));\n\t\t\tif (this.skippingOversizedLine) this.resetLine();\n\t\t\telse lines.push(this.takeLine());\n\t\t\tif (char === \"\\r\") {\n\t\t\t\tif (text[index + 1] === \"\\n\") index++;\n\t\t\t\telse if (index + 1 === text.length) this.skipLeadingLf = true;\n\t\t\t}\n\t\t\tstart = index + 1;\n\t\t}\n\t\tthis.append(text.slice(start));\n\t\treturn lines;\n\t}\n\n\tfinish(): string | undefined {\n\t\tthis.skipLeadingLf = false;\n\t\tif (this.skippingOversizedLine) {\n\t\t\tthis.resetLine();\n\t\t\treturn undefined;\n\t\t}\n\t\tif (this.length === 0 && this.parts.length === 0) return undefined;\n\t\treturn this.takeLine();\n\t}\n\n\tprivate append(part: string): void {\n\t\tif (part.length === 0 || this.skippingOversizedLine) return;\n\t\tthis.length += part.length;\n\t\tif (this.length > this.maxLineChars) {\n\t\t\tif (this.overflow === \"throw\") {\n\t\t\t\tthrow new Error(`Stream exceeded the ${this.maxLineChars} character line limit`);\n\t\t\t}\n\t\t\tthis.parts = [];\n\t\t\tthis.length = 0;\n\t\t\tthis.skippingOversizedLine = true;\n\t\t\treturn;\n\t\t}\n\t\tthis.parts.push(part);\n\t}\n\n\tprivate takeLine(): string {\n\t\tconst line = this.parts.length === 0 ? \"\" : this.parts.length === 1 ? this.parts[0] : this.parts.join(\"\");\n\t\tthis.resetLine();\n\t\treturn line;\n\t}\n\n\tprivate resetLine(): void {\n\t\tthis.parts = [];\n\t\tthis.length = 0;\n\t\tthis.skippingOversizedLine = false;\n\t}\n}\n"]}