@theokit/sdk-tools 0.25.0 → 0.26.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/dist/index.cjs +27 -6
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +39 -4
- package/dist/index.d.ts +39 -4
- package/dist/index.js +27 -6
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.d.cts
CHANGED
|
@@ -1099,24 +1099,59 @@ declare function todoItemsToPlanNodes(items: readonly TodoItem[]): PlanNode[];
|
|
|
1099
1099
|
/**
|
|
1100
1100
|
* `truncateOutput` — utility for truncating large tool output.
|
|
1101
1101
|
*
|
|
1102
|
-
* When output exceeds `maxBytes`, writes the full content to a temp file
|
|
1103
|
-
*
|
|
1102
|
+
* When output exceeds `maxBytes`, writes the full content to a temp file and returns a truncated
|
|
1103
|
+
* version with a reference to the full output.
|
|
1104
|
+
*
|
|
1105
|
+
* ## M77 — why this grew instead of a new truncator being written
|
|
1106
|
+
*
|
|
1107
|
+
* The M77 discovery measured **six** output ceilings across `sdk-tools`, with five different values
|
|
1108
|
+
* and no coordination between them (`web-fetch.ts` 1 MB, `shell-exec.ts` 5 MB, `git-diff.ts` 5 MB,
|
|
1109
|
+
* `run-vitest.ts` 10 MB, `git-status.ts` configurable, and this helper's 30 000 B). It also found
|
|
1110
|
+
* that this helper — exported from the barrel — had **zero production consumers**: the SDK had
|
|
1111
|
+
* already paid for a shared truncator that nobody used while every tool rolled its own.
|
|
1112
|
+
*
|
|
1113
|
+
* Adding a seventh ceiling in the runtime would have been the defect, not the fix. So `toolResultBudget`
|
|
1114
|
+
* extends this one instead (parsimony-ladder rung 4), gaining the two things it lacked:
|
|
1115
|
+
*
|
|
1116
|
+
* - **`head-tail` mode** — the previous cut was head-only, which for command output discards
|
|
1117
|
+
* exactly the part that matters: the error, the summary, the final prompt.
|
|
1118
|
+
* - **`originalBytes`** — a machine-readable marker. The only prior signal was an English sentence
|
|
1119
|
+
* injected into the text (`"[Output truncated. Full output: …]"`), so a consumer wanting to know
|
|
1120
|
+
* how much was lost had to parse prose.
|
|
1121
|
+
*
|
|
1122
|
+
* Consolidating the MECHANISM is deliberately not the same as unifying the VALUES: 1 MB for
|
|
1123
|
+
* `web-fetch` and 10 MB for `run-vitest` may differ for good reasons. That second step is out of
|
|
1124
|
+
* scope here (plan ADR D2).
|
|
1104
1125
|
*
|
|
1105
1126
|
* Return shape:
|
|
1106
|
-
* - `{ content
|
|
1107
|
-
* - `{ content
|
|
1127
|
+
* - `{ content, truncated: false, originalBytes }`
|
|
1128
|
+
* - `{ content, truncated: true, originalBytes, overflowPath }`
|
|
1108
1129
|
*/
|
|
1130
|
+
/** How the middle is dropped when output exceeds the budget. */
|
|
1131
|
+
type TruncationMode = "head" | "head-tail";
|
|
1109
1132
|
interface TruncationOptions {
|
|
1110
1133
|
/** Maximum output size in bytes before truncation. Default: 30_000. */
|
|
1111
1134
|
maxBytes?: number;
|
|
1112
1135
|
/** Directory for overflow files. Default: ".theocode/tool-output". */
|
|
1113
1136
|
outputDir?: string;
|
|
1137
|
+
/**
|
|
1138
|
+
* `"head"` (default) keeps the first `maxBytes` — the historical behaviour, preserved because this
|
|
1139
|
+
* helper is public API. `"head-tail"` splits the budget between the start and the END, which is
|
|
1140
|
+
* where command output usually carries its conclusion.
|
|
1141
|
+
*/
|
|
1142
|
+
mode?: TruncationMode;
|
|
1114
1143
|
}
|
|
1115
1144
|
interface TruncationResult {
|
|
1116
1145
|
/** The (possibly truncated) content. */
|
|
1117
1146
|
content: string;
|
|
1118
1147
|
/** Whether the output was truncated. */
|
|
1119
1148
|
truncated: boolean;
|
|
1149
|
+
/**
|
|
1150
|
+
* Byte length of the ORIGINAL output, present on both paths. Always present by design: a field
|
|
1151
|
+
* that only appears on failure forces every consumer through an `undefined` check, which is the
|
|
1152
|
+
* door magic values come in through.
|
|
1153
|
+
*/
|
|
1154
|
+
originalBytes: number;
|
|
1120
1155
|
/** Path to the full output file, present only when truncated. */
|
|
1121
1156
|
overflowPath?: string;
|
|
1122
1157
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -1099,24 +1099,59 @@ declare function todoItemsToPlanNodes(items: readonly TodoItem[]): PlanNode[];
|
|
|
1099
1099
|
/**
|
|
1100
1100
|
* `truncateOutput` — utility for truncating large tool output.
|
|
1101
1101
|
*
|
|
1102
|
-
* When output exceeds `maxBytes`, writes the full content to a temp file
|
|
1103
|
-
*
|
|
1102
|
+
* When output exceeds `maxBytes`, writes the full content to a temp file and returns a truncated
|
|
1103
|
+
* version with a reference to the full output.
|
|
1104
|
+
*
|
|
1105
|
+
* ## M77 — why this grew instead of a new truncator being written
|
|
1106
|
+
*
|
|
1107
|
+
* The M77 discovery measured **six** output ceilings across `sdk-tools`, with five different values
|
|
1108
|
+
* and no coordination between them (`web-fetch.ts` 1 MB, `shell-exec.ts` 5 MB, `git-diff.ts` 5 MB,
|
|
1109
|
+
* `run-vitest.ts` 10 MB, `git-status.ts` configurable, and this helper's 30 000 B). It also found
|
|
1110
|
+
* that this helper — exported from the barrel — had **zero production consumers**: the SDK had
|
|
1111
|
+
* already paid for a shared truncator that nobody used while every tool rolled its own.
|
|
1112
|
+
*
|
|
1113
|
+
* Adding a seventh ceiling in the runtime would have been the defect, not the fix. So `toolResultBudget`
|
|
1114
|
+
* extends this one instead (parsimony-ladder rung 4), gaining the two things it lacked:
|
|
1115
|
+
*
|
|
1116
|
+
* - **`head-tail` mode** — the previous cut was head-only, which for command output discards
|
|
1117
|
+
* exactly the part that matters: the error, the summary, the final prompt.
|
|
1118
|
+
* - **`originalBytes`** — a machine-readable marker. The only prior signal was an English sentence
|
|
1119
|
+
* injected into the text (`"[Output truncated. Full output: …]"`), so a consumer wanting to know
|
|
1120
|
+
* how much was lost had to parse prose.
|
|
1121
|
+
*
|
|
1122
|
+
* Consolidating the MECHANISM is deliberately not the same as unifying the VALUES: 1 MB for
|
|
1123
|
+
* `web-fetch` and 10 MB for `run-vitest` may differ for good reasons. That second step is out of
|
|
1124
|
+
* scope here (plan ADR D2).
|
|
1104
1125
|
*
|
|
1105
1126
|
* Return shape:
|
|
1106
|
-
* - `{ content
|
|
1107
|
-
* - `{ content
|
|
1127
|
+
* - `{ content, truncated: false, originalBytes }`
|
|
1128
|
+
* - `{ content, truncated: true, originalBytes, overflowPath }`
|
|
1108
1129
|
*/
|
|
1130
|
+
/** How the middle is dropped when output exceeds the budget. */
|
|
1131
|
+
type TruncationMode = "head" | "head-tail";
|
|
1109
1132
|
interface TruncationOptions {
|
|
1110
1133
|
/** Maximum output size in bytes before truncation. Default: 30_000. */
|
|
1111
1134
|
maxBytes?: number;
|
|
1112
1135
|
/** Directory for overflow files. Default: ".theocode/tool-output". */
|
|
1113
1136
|
outputDir?: string;
|
|
1137
|
+
/**
|
|
1138
|
+
* `"head"` (default) keeps the first `maxBytes` — the historical behaviour, preserved because this
|
|
1139
|
+
* helper is public API. `"head-tail"` splits the budget between the start and the END, which is
|
|
1140
|
+
* where command output usually carries its conclusion.
|
|
1141
|
+
*/
|
|
1142
|
+
mode?: TruncationMode;
|
|
1114
1143
|
}
|
|
1115
1144
|
interface TruncationResult {
|
|
1116
1145
|
/** The (possibly truncated) content. */
|
|
1117
1146
|
content: string;
|
|
1118
1147
|
/** Whether the output was truncated. */
|
|
1119
1148
|
truncated: boolean;
|
|
1149
|
+
/**
|
|
1150
|
+
* Byte length of the ORIGINAL output, present on both paths. Always present by design: a field
|
|
1151
|
+
* that only appears on failure forces every consumer through an `undefined` check, which is the
|
|
1152
|
+
* door magic values come in through.
|
|
1153
|
+
*/
|
|
1154
|
+
originalBytes: number;
|
|
1120
1155
|
/** Path to the full output file, present only when truncated. */
|
|
1121
1156
|
overflowPath?: string;
|
|
1122
1157
|
}
|
package/dist/index.js
CHANGED
|
@@ -11,6 +11,7 @@ import { spawn } from 'child_process';
|
|
|
11
11
|
import { resolveInteractive, InteractiveUnavailableError, NoSuchSessionError } from '@theokit/sdk/interactive';
|
|
12
12
|
import { lookup } from 'dns/promises';
|
|
13
13
|
import { isIP } from 'net';
|
|
14
|
+
import { randomUUID } from 'crypto';
|
|
14
15
|
|
|
15
16
|
// src/apply-patch.ts
|
|
16
17
|
|
|
@@ -2543,24 +2544,44 @@ function createTodolistTool() {
|
|
|
2543
2544
|
getItems: (threadId) => ops(threadId).getItems()
|
|
2544
2545
|
};
|
|
2545
2546
|
}
|
|
2547
|
+
function decodeWholeCodePoints(buf) {
|
|
2548
|
+
return new TextDecoder("utf-8").decode(buf, { stream: true });
|
|
2549
|
+
}
|
|
2546
2550
|
function truncateOutput(output, opts) {
|
|
2547
2551
|
const maxBytes = opts?.maxBytes ?? 3e4;
|
|
2548
2552
|
const outputDir = opts?.outputDir ?? ".theocode/tool-output";
|
|
2549
|
-
const
|
|
2550
|
-
|
|
2551
|
-
|
|
2553
|
+
const mode = opts?.mode ?? "head";
|
|
2554
|
+
const originalBytes = Buffer.byteLength(output, "utf-8");
|
|
2555
|
+
if (originalBytes <= maxBytes) {
|
|
2556
|
+
return { content: output, truncated: false, originalBytes };
|
|
2552
2557
|
}
|
|
2553
2558
|
mkdirSync(outputDir, { recursive: true });
|
|
2554
|
-
const filename = `overflow-${Date.now()}.txt`;
|
|
2559
|
+
const filename = `overflow-${Date.now()}-${randomUUID().slice(0, 8)}.txt`;
|
|
2555
2560
|
const overflowPath = join(outputDir, filename);
|
|
2556
2561
|
writeFileSync(overflowPath, output, "utf-8");
|
|
2557
|
-
const
|
|
2562
|
+
const buf = Buffer.from(output, "utf-8");
|
|
2558
2563
|
const trailer = `
|
|
2559
2564
|
|
|
2560
2565
|
[Output truncated. Full output: ${overflowPath}]`;
|
|
2566
|
+
if (mode === "head-tail") {
|
|
2567
|
+
const half = Math.floor(maxBytes / 2);
|
|
2568
|
+
const head = decodeWholeCodePoints(buf.subarray(0, half));
|
|
2569
|
+
const tail = decodeWholeCodePoints(buf.subarray(buf.length - half));
|
|
2570
|
+
return {
|
|
2571
|
+
content: `${head}
|
|
2572
|
+
|
|
2573
|
+
[\u2026 ${String(originalBytes - maxBytes)} bytes omitted \u2026]
|
|
2574
|
+
|
|
2575
|
+
${tail}${trailer}`,
|
|
2576
|
+
truncated: true,
|
|
2577
|
+
originalBytes,
|
|
2578
|
+
overflowPath
|
|
2579
|
+
};
|
|
2580
|
+
}
|
|
2561
2581
|
return {
|
|
2562
|
-
content:
|
|
2582
|
+
content: decodeWholeCodePoints(buf.subarray(0, maxBytes)) + trailer,
|
|
2563
2583
|
truncated: true,
|
|
2584
|
+
originalBytes,
|
|
2564
2585
|
overflowPath
|
|
2565
2586
|
};
|
|
2566
2587
|
}
|