zidane 3.1.0 → 3.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +110 -7
- package/dist/{agent-O5SmWixc.d.ts → agent-C9q5VMGa.d.ts} +279 -3
- package/dist/{chunk-PYLYK3K7.js → chunk-2AE3VM5O.js} +547 -35
- package/dist/{chunk-R74LQKAM.js → chunk-7H34OFDA.js} +26 -0
- package/dist/{chunk-FEAOZ5DB.js → chunk-BRMURQA2.js} +12 -3
- package/dist/{chunk-SARMZCEL.js → chunk-BXO7CZHJ.js} +1 -1
- package/dist/{chunk-2EQT4EHD.js → chunk-IUBBVF53.js} +4 -0
- package/dist/{chunk-VF4A7HAC.js → chunk-YQ7LY6CL.js} +3 -0
- package/dist/contexts.d.ts +3 -3
- package/dist/contexts.js +1 -1
- package/dist/index.d.ts +6 -6
- package/dist/index.js +6 -6
- package/dist/mcp.d.ts +2 -2
- package/dist/mcp.js +1 -1
- package/dist/presets.d.ts +2 -2
- package/dist/presets.js +4 -4
- package/dist/providers.d.ts +2 -2
- package/dist/providers.js +2 -2
- package/dist/{sandbox-CW72eLDP.d.ts → sandbox-CLghrTLi.d.ts} +1 -1
- package/dist/session/sqlite.d.ts +2 -2
- package/dist/session.d.ts +2 -2
- package/dist/session.js +1 -1
- package/dist/{skills-use-DJKUctM5.d.ts → skills-use-DU0unNP4.d.ts} +1 -1
- package/dist/skills.d.ts +3 -3
- package/dist/tools.d.ts +5 -5
- package/dist/tools.js +3 -3
- package/dist/{types-BpvTmawk.d.ts → types-vA1a_ZX7.d.ts} +11 -0
- package/dist/types.d.ts +4 -4
- package/dist/{validation-DE4g5Cez.d.ts → validation-BKA33eqb.d.ts} +1 -1
- package/package.json +1 -1
|
@@ -8,10 +8,10 @@ import {
|
|
|
8
8
|
} from "./chunk-TPXPVEH6.js";
|
|
9
9
|
import {
|
|
10
10
|
createProcessContext
|
|
11
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-IUBBVF53.js";
|
|
12
12
|
import {
|
|
13
13
|
connectMcpServers
|
|
14
|
-
} from "./chunk-
|
|
14
|
+
} from "./chunk-7H34OFDA.js";
|
|
15
15
|
import {
|
|
16
16
|
toolOutputByteLength
|
|
17
17
|
} from "./chunk-JH6IAAFA.js";
|
|
@@ -36,6 +36,154 @@ function countExactMatches(haystack, needle) {
|
|
|
36
36
|
}
|
|
37
37
|
return count;
|
|
38
38
|
}
|
|
39
|
+
var LEFT_SINGLE_CURLY_QUOTE = "\u2018";
|
|
40
|
+
var RIGHT_SINGLE_CURLY_QUOTE = "\u2019";
|
|
41
|
+
var LEFT_DOUBLE_CURLY_QUOTE = "\u201C";
|
|
42
|
+
var RIGHT_DOUBLE_CURLY_QUOTE = "\u201D";
|
|
43
|
+
function normalizeQuotes(str) {
|
|
44
|
+
return str.replaceAll(LEFT_SINGLE_CURLY_QUOTE, "'").replaceAll(RIGHT_SINGLE_CURLY_QUOTE, "'").replaceAll(LEFT_DOUBLE_CURLY_QUOTE, '"').replaceAll(RIGHT_DOUBLE_CURLY_QUOTE, '"');
|
|
45
|
+
}
|
|
46
|
+
var DESANITIZATIONS = [
|
|
47
|
+
["<fnr>", "<function_results>"],
|
|
48
|
+
["<n>", "<name>"],
|
|
49
|
+
["</n>", "</name>"],
|
|
50
|
+
["<o>", "<output>"],
|
|
51
|
+
["</o>", "</output>"],
|
|
52
|
+
["<e>", "<error>"],
|
|
53
|
+
["</e>", "</error>"],
|
|
54
|
+
["<s>", "<system>"],
|
|
55
|
+
["</s>", "</system>"],
|
|
56
|
+
["<r>", "<result>"],
|
|
57
|
+
["</r>", "</result>"],
|
|
58
|
+
["< META_START >", "<META_START>"],
|
|
59
|
+
["< META_END >", "<META_END>"],
|
|
60
|
+
["< EOT >", "<EOT>"],
|
|
61
|
+
["< META >", "<META>"],
|
|
62
|
+
["< SOS >", "<SOS>"],
|
|
63
|
+
["\n\nH:", "\n\nHuman:"],
|
|
64
|
+
["\n\nA:", "\n\nAssistant:"]
|
|
65
|
+
];
|
|
66
|
+
function desanitize(s) {
|
|
67
|
+
let out = s;
|
|
68
|
+
for (const [from, to] of DESANITIZATIONS)
|
|
69
|
+
out = out.replaceAll(from, to);
|
|
70
|
+
return out;
|
|
71
|
+
}
|
|
72
|
+
function resolveOldString(haystack, needle) {
|
|
73
|
+
const exact = countExactMatches(haystack, needle);
|
|
74
|
+
if (exact > 0)
|
|
75
|
+
return { actual: needle, occurrences: exact, via: "exact" };
|
|
76
|
+
const normNeedle = normalizeQuotes(needle);
|
|
77
|
+
const normFile = normalizeQuotes(haystack);
|
|
78
|
+
if (normNeedle !== needle || normFile !== haystack) {
|
|
79
|
+
const idx = normFile.indexOf(normNeedle);
|
|
80
|
+
if (idx !== -1) {
|
|
81
|
+
const actual = haystack.slice(idx, idx + normNeedle.length);
|
|
82
|
+
let occ = 0;
|
|
83
|
+
let cursor = 0;
|
|
84
|
+
while (true) {
|
|
85
|
+
const next = normFile.indexOf(normNeedle, cursor);
|
|
86
|
+
if (next === -1)
|
|
87
|
+
break;
|
|
88
|
+
occ++;
|
|
89
|
+
cursor = next + normNeedle.length;
|
|
90
|
+
}
|
|
91
|
+
return { actual, occurrences: occ, via: "quotes" };
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
const desan = desanitize(needle);
|
|
95
|
+
if (desan !== needle) {
|
|
96
|
+
const desanCount = countExactMatches(haystack, desan);
|
|
97
|
+
if (desanCount > 0)
|
|
98
|
+
return { actual: desan, occurrences: desanCount, via: "desanitize" };
|
|
99
|
+
}
|
|
100
|
+
const combo = desanitize(normNeedle);
|
|
101
|
+
if (combo !== needle) {
|
|
102
|
+
const idx = normFile.indexOf(combo);
|
|
103
|
+
if (idx !== -1) {
|
|
104
|
+
const actual = haystack.slice(idx, idx + combo.length);
|
|
105
|
+
let occ = 0;
|
|
106
|
+
let cursor = 0;
|
|
107
|
+
while (true) {
|
|
108
|
+
const next = normFile.indexOf(combo, cursor);
|
|
109
|
+
if (next === -1)
|
|
110
|
+
break;
|
|
111
|
+
occ++;
|
|
112
|
+
cursor = next + combo.length;
|
|
113
|
+
}
|
|
114
|
+
return { actual, occurrences: occ, via: "quotes+desanitize" };
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
return null;
|
|
118
|
+
}
|
|
119
|
+
function preserveQuoteStyle(actual, replacement) {
|
|
120
|
+
const hasDouble = actual.includes(LEFT_DOUBLE_CURLY_QUOTE) || actual.includes(RIGHT_DOUBLE_CURLY_QUOTE);
|
|
121
|
+
const hasSingle = actual.includes(LEFT_SINGLE_CURLY_QUOTE) || actual.includes(RIGHT_SINGLE_CURLY_QUOTE);
|
|
122
|
+
if (!hasDouble && !hasSingle)
|
|
123
|
+
return replacement;
|
|
124
|
+
let out = replacement;
|
|
125
|
+
if (hasDouble)
|
|
126
|
+
out = applyCurly(out, '"', LEFT_DOUBLE_CURLY_QUOTE, RIGHT_DOUBLE_CURLY_QUOTE, false);
|
|
127
|
+
if (hasSingle)
|
|
128
|
+
out = applyCurly(out, "'", LEFT_SINGLE_CURLY_QUOTE, RIGHT_SINGLE_CURLY_QUOTE, true);
|
|
129
|
+
return out;
|
|
130
|
+
}
|
|
131
|
+
function applyCurly(s, straight, left, right, contractionAware) {
|
|
132
|
+
const chars = [...s];
|
|
133
|
+
const result = [];
|
|
134
|
+
for (let i = 0; i < chars.length; i++) {
|
|
135
|
+
if (chars[i] !== straight) {
|
|
136
|
+
result.push(chars[i]);
|
|
137
|
+
continue;
|
|
138
|
+
}
|
|
139
|
+
if (contractionAware) {
|
|
140
|
+
const prev = i > 0 ? chars[i - 1] : "";
|
|
141
|
+
const next = i < chars.length - 1 ? chars[i + 1] : "";
|
|
142
|
+
if (/\p{L}/u.test(prev) && /\p{L}/u.test(next)) {
|
|
143
|
+
result.push(right);
|
|
144
|
+
continue;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
result.push(isOpeningContext(chars, i) ? left : right);
|
|
148
|
+
}
|
|
149
|
+
return result.join("");
|
|
150
|
+
}
|
|
151
|
+
function isOpeningContext(chars, i) {
|
|
152
|
+
if (i === 0)
|
|
153
|
+
return true;
|
|
154
|
+
const prev = chars[i - 1];
|
|
155
|
+
return prev === " " || prev === " " || prev === "\n" || prev === "\r" || prev === "(" || prev === "[" || prev === "{" || prev === "\u2014" || prev === "\u2013";
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// src/tools/path-suggest.ts
|
|
159
|
+
async function findSimilarFile(execution, handle, path) {
|
|
160
|
+
const slash = path.lastIndexOf("/");
|
|
161
|
+
const dir = slash === -1 ? "." : path.slice(0, slash) || "/";
|
|
162
|
+
const target = slash === -1 ? path : path.slice(slash + 1);
|
|
163
|
+
const dot = target.lastIndexOf(".");
|
|
164
|
+
const targetBase = dot === -1 ? target : target.slice(0, dot);
|
|
165
|
+
if (targetBase.length === 0)
|
|
166
|
+
return null;
|
|
167
|
+
let entries;
|
|
168
|
+
try {
|
|
169
|
+
entries = await execution.listFiles(handle, dir);
|
|
170
|
+
} catch {
|
|
171
|
+
return null;
|
|
172
|
+
}
|
|
173
|
+
for (const entry of entries) {
|
|
174
|
+
if (entry === target)
|
|
175
|
+
continue;
|
|
176
|
+
const entryDot = entry.lastIndexOf(".");
|
|
177
|
+
const entryBase = entryDot === -1 ? entry : entry.slice(0, entryDot);
|
|
178
|
+
if (entryBase === targetBase)
|
|
179
|
+
return entry;
|
|
180
|
+
}
|
|
181
|
+
return null;
|
|
182
|
+
}
|
|
183
|
+
async function suggestionFor(execution, handle, path) {
|
|
184
|
+
const sibling = await findSimilarFile(execution, handle, path);
|
|
185
|
+
return sibling ? ` Did you mean ${sibling}?` : "";
|
|
186
|
+
}
|
|
39
187
|
|
|
40
188
|
// src/tools/read-state.ts
|
|
41
189
|
var STATE = /* @__PURE__ */ new WeakMap();
|
|
@@ -49,6 +197,17 @@ function getReadState(session) {
|
|
|
49
197
|
}
|
|
50
198
|
return map;
|
|
51
199
|
}
|
|
200
|
+
var TOOL_DEDUP_STATE = /* @__PURE__ */ new WeakMap();
|
|
201
|
+
function getToolDedupState(session) {
|
|
202
|
+
if (!session)
|
|
203
|
+
return void 0;
|
|
204
|
+
let map = TOOL_DEDUP_STATE.get(session);
|
|
205
|
+
if (!map) {
|
|
206
|
+
map = /* @__PURE__ */ new Map();
|
|
207
|
+
TOOL_DEDUP_STATE.set(session, map);
|
|
208
|
+
}
|
|
209
|
+
return map;
|
|
210
|
+
}
|
|
52
211
|
function hashContent(text) {
|
|
53
212
|
let h = 2166136261;
|
|
54
213
|
for (let i = 0; i < text.length; i++) {
|
|
@@ -87,7 +246,8 @@ var edit = {
|
|
|
87
246
|
try {
|
|
88
247
|
original = await ctx.execution.readFile(ctx.handle, target);
|
|
89
248
|
} catch {
|
|
90
|
-
|
|
249
|
+
const hint = await suggestionFor(ctx.execution, ctx.handle, target);
|
|
250
|
+
return `Edit error: file not found: ${target}.${hint}`;
|
|
91
251
|
}
|
|
92
252
|
if (ctx.behavior?.requireReadBeforeEdit && ctx.session) {
|
|
93
253
|
const readState = getReadState(ctx.session);
|
|
@@ -98,14 +258,18 @@ var edit = {
|
|
|
98
258
|
if (prior.contentHash !== hashContent(original))
|
|
99
259
|
return `Edit error: ${target} has changed on disk since the last read. Re-read the file before editing.`;
|
|
100
260
|
}
|
|
101
|
-
const
|
|
102
|
-
if (
|
|
261
|
+
const match = resolveOldString(original, find);
|
|
262
|
+
if (!match) {
|
|
103
263
|
const preview = nearestMatchPreview(original, find);
|
|
104
264
|
return preview ? `Edit error: old_string not found in ${target}. Closest match in the file: ${preview}` : `Edit error: old_string not found in ${target}.`;
|
|
105
265
|
}
|
|
266
|
+
const { actual, occurrences, via } = match;
|
|
106
267
|
if (occurrences > 1 && !replaceAll)
|
|
107
268
|
return `Edit error: old_string appears ${occurrences} times in ${target}. Pass replace_all=true or expand old_string for uniqueness.`;
|
|
108
|
-
|
|
269
|
+
let styledReplacement = via === "desanitize" || via === "quotes+desanitize" ? desanitize(replacement) : replacement;
|
|
270
|
+
if (via === "quotes" || via === "quotes+desanitize")
|
|
271
|
+
styledReplacement = preserveQuoteStyle(actual, styledReplacement);
|
|
272
|
+
const updated = replaceAll ? original.split(actual).join(styledReplacement) : original.replace(actual, styledReplacement);
|
|
109
273
|
if (updated === original)
|
|
110
274
|
return `Edit error: replacement produced no change in ${target}.`;
|
|
111
275
|
await ctx.execution.writeFile(ctx.handle, target, updated);
|
|
@@ -479,7 +643,8 @@ var multiEdit = {
|
|
|
479
643
|
try {
|
|
480
644
|
current = await ctx.execution.readFile(ctx.handle, target);
|
|
481
645
|
} catch {
|
|
482
|
-
|
|
646
|
+
const hint = await suggestionFor(ctx.execution, ctx.handle, target);
|
|
647
|
+
return `multi_edit error: file not found: ${target}.${hint}`;
|
|
483
648
|
}
|
|
484
649
|
if (ctx.behavior?.requireReadBeforeEdit && ctx.session) {
|
|
485
650
|
const readState = getReadState(ctx.session);
|
|
@@ -502,12 +667,16 @@ var multiEdit = {
|
|
|
502
667
|
return `multi_edit error: edit #${i + 1} has empty old_string. Use write_file to fully replace a file.`;
|
|
503
668
|
if (find === replacement)
|
|
504
669
|
return `multi_edit error: edit #${i + 1} old_string and new_string are identical.`;
|
|
505
|
-
const
|
|
506
|
-
if (
|
|
670
|
+
const match = resolveOldString(current, find);
|
|
671
|
+
if (!match)
|
|
507
672
|
return `multi_edit error: edit #${i + 1} old_string not found in ${target}.`;
|
|
673
|
+
const { actual, occurrences, via } = match;
|
|
508
674
|
if (occurrences > 1 && !replaceAll)
|
|
509
675
|
return `multi_edit error: edit #${i + 1} old_string appears ${occurrences} times. Pass replace_all=true on this edit or expand old_string for uniqueness.`;
|
|
510
|
-
|
|
676
|
+
let styledReplacement = via === "desanitize" || via === "quotes+desanitize" ? desanitize(replacement) : replacement;
|
|
677
|
+
if (via === "quotes" || via === "quotes+desanitize")
|
|
678
|
+
styledReplacement = preserveQuoteStyle(actual, styledReplacement);
|
|
679
|
+
current = replaceAll ? current.split(actual).join(styledReplacement) : current.replace(actual, styledReplacement);
|
|
511
680
|
applied += occurrences;
|
|
512
681
|
}
|
|
513
682
|
await ctx.execution.writeFile(ctx.handle, target, current);
|
|
@@ -524,10 +693,51 @@ var multiEdit = {
|
|
|
524
693
|
};
|
|
525
694
|
|
|
526
695
|
// src/tools/read-file.ts
|
|
696
|
+
import { Buffer as Buffer2 } from "buffer";
|
|
697
|
+
|
|
698
|
+
// src/tools/binary-read.ts
|
|
527
699
|
import { Buffer } from "buffer";
|
|
700
|
+
function imageMediaTypeFor(path) {
|
|
701
|
+
const dot = path.lastIndexOf(".");
|
|
702
|
+
if (dot === -1)
|
|
703
|
+
return void 0;
|
|
704
|
+
const ext = path.slice(dot + 1).toLowerCase();
|
|
705
|
+
switch (ext) {
|
|
706
|
+
case "png":
|
|
707
|
+
return "image/png";
|
|
708
|
+
case "jpg":
|
|
709
|
+
case "jpeg":
|
|
710
|
+
return "image/jpeg";
|
|
711
|
+
case "gif":
|
|
712
|
+
return "image/gif";
|
|
713
|
+
case "webp":
|
|
714
|
+
return "image/webp";
|
|
715
|
+
default:
|
|
716
|
+
return void 0;
|
|
717
|
+
}
|
|
718
|
+
}
|
|
719
|
+
async function readFileAsBase64(execution, handle, path) {
|
|
720
|
+
if (execution.readFileBinary) {
|
|
721
|
+
const bytes = await execution.readFileBinary(handle, path);
|
|
722
|
+
const b642 = Buffer.from(bytes.buffer, bytes.byteOffset, bytes.byteLength).toString("base64");
|
|
723
|
+
return { base64: b642, byteLength: bytes.byteLength };
|
|
724
|
+
}
|
|
725
|
+
const cmd = `base64 < ${shellQuote2(path)}`;
|
|
726
|
+
const result = await execution.exec(handle, cmd);
|
|
727
|
+
if (result.exitCode !== 0)
|
|
728
|
+
throw new Error(`base64 read failed: ${result.stderr || `exit ${result.exitCode}`}`);
|
|
729
|
+
const b64 = result.stdout.replace(/\s+/g, "");
|
|
730
|
+
return { base64: b64, byteLength: Math.floor(b64.length * 3 / 4) };
|
|
731
|
+
}
|
|
732
|
+
function shellQuote2(s) {
|
|
733
|
+
return `'${s.replace(/'/g, "'\\''")}'`;
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
// src/tools/read-file.ts
|
|
528
737
|
var DEFAULT_LINE_LIMIT = 2e3;
|
|
529
738
|
var DEFAULT_BYTE_CAP = 65536;
|
|
530
739
|
var BINARY_PROBE_BYTES = 8e3;
|
|
740
|
+
var DEFAULT_IMAGE_BYTE_CAP = 5 * 1024 * 1024;
|
|
531
741
|
var readFile = {
|
|
532
742
|
spec: {
|
|
533
743
|
name: "read_file",
|
|
@@ -544,13 +754,33 @@ var readFile = {
|
|
|
544
754
|
}
|
|
545
755
|
},
|
|
546
756
|
async execute({ path, offset, limit, maxBytes }, ctx) {
|
|
757
|
+
const imgMedia = imageMediaTypeFor(path);
|
|
758
|
+
if (imgMedia) {
|
|
759
|
+
const sizeCap = maxBytes !== void 0 ? normalizeInteger(maxBytes, DEFAULT_IMAGE_BYTE_CAP) : DEFAULT_IMAGE_BYTE_CAP;
|
|
760
|
+
try {
|
|
761
|
+
const { base64, byteLength } = await readFileAsBase64(ctx.execution, ctx.handle, path);
|
|
762
|
+
if (sizeCap > 0 && byteLength > sizeCap) {
|
|
763
|
+
return `[image too large to inline: ${path}, ${byteLength} bytes (cap ${sizeCap}). Raise maxBytes, or use shell to inspect.]`;
|
|
764
|
+
}
|
|
765
|
+
const content = [
|
|
766
|
+
{ type: "text", text: `Image: ${path} (${byteLength} bytes, ${imgMedia})` },
|
|
767
|
+
{ type: "image", mediaType: imgMedia, data: base64 }
|
|
768
|
+
];
|
|
769
|
+
return content;
|
|
770
|
+
} catch (err) {
|
|
771
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
772
|
+
const hint = await suggestionFor(ctx.execution, ctx.handle, path);
|
|
773
|
+
return `Image read failed: ${path} \u2014 ${msg}.${hint}`;
|
|
774
|
+
}
|
|
775
|
+
}
|
|
547
776
|
let raw;
|
|
548
777
|
try {
|
|
549
778
|
raw = await ctx.execution.readFile(ctx.handle, path);
|
|
550
779
|
} catch {
|
|
551
|
-
|
|
780
|
+
const hint = await suggestionFor(ctx.execution, ctx.handle, path);
|
|
781
|
+
return `File not found: ${path}.${hint}`;
|
|
552
782
|
}
|
|
553
|
-
const totalBytes =
|
|
783
|
+
const totalBytes = Buffer2.byteLength(raw);
|
|
554
784
|
const dedupEnabled = ctx.behavior?.dedupReads !== false;
|
|
555
785
|
const readState = dedupEnabled ? getReadState(ctx.session) : void 0;
|
|
556
786
|
const absKey = `${ctx.handle.cwd}::${path}`;
|
|
@@ -580,7 +810,7 @@ var readFile = {
|
|
|
580
810
|
if (maxBytesN > 0) {
|
|
581
811
|
const truncatedSlice = [];
|
|
582
812
|
for (const line of slice) {
|
|
583
|
-
const lineBytes =
|
|
813
|
+
const lineBytes = Buffer2.byteLength(line) + 1;
|
|
584
814
|
if (bytesUsed + lineBytes > maxBytesN && truncatedSlice.length > 0) {
|
|
585
815
|
bytesCut = true;
|
|
586
816
|
break;
|
|
@@ -597,14 +827,14 @@ var readFile = {
|
|
|
597
827
|
}
|
|
598
828
|
let midLineCut = false;
|
|
599
829
|
if (maxBytesN > 0 && slice.length > 0) {
|
|
600
|
-
const bodyBytes =
|
|
830
|
+
const bodyBytes = Buffer2.byteLength(slice.join("\n"));
|
|
601
831
|
if (bodyBytes > maxBytesN) {
|
|
602
832
|
const lastIdx = slice.length - 1;
|
|
603
833
|
const lastLine = slice[lastIdx];
|
|
604
|
-
const otherBytes = lastIdx > 0 ?
|
|
834
|
+
const otherBytes = lastIdx > 0 ? Buffer2.byteLength(slice.slice(0, lastIdx).join("\n")) + 1 : 0;
|
|
605
835
|
const budgetForLast = Math.max(0, maxBytesN - otherBytes);
|
|
606
836
|
let cut = Math.min(lastLine.length, budgetForLast);
|
|
607
|
-
while (cut > 0 &&
|
|
837
|
+
while (cut > 0 && Buffer2.byteLength(lastLine.slice(0, cut)) > budgetForLast)
|
|
608
838
|
cut--;
|
|
609
839
|
slice[lastIdx] = lastLine.slice(0, cut);
|
|
610
840
|
midLineCut = true;
|
|
@@ -666,7 +896,38 @@ function looksBinary(text) {
|
|
|
666
896
|
}
|
|
667
897
|
|
|
668
898
|
// src/tools/shell.ts
|
|
669
|
-
import { Buffer as
|
|
899
|
+
import { Buffer as Buffer3 } from "buffer";
|
|
900
|
+
|
|
901
|
+
// src/tools/shell-semantics.ts
|
|
902
|
+
var DEFAULT_SEMANTIC = (exitCode) => ({
|
|
903
|
+
isError: exitCode !== 0,
|
|
904
|
+
message: exitCode !== 0 ? `Command failed with exit code ${exitCode}` : void 0
|
|
905
|
+
});
|
|
906
|
+
var COMMAND_SEMANTICS = /* @__PURE__ */ new Map([
|
|
907
|
+
// grep / ripgrep: 0 = matches, 1 = no matches, ≥2 = error.
|
|
908
|
+
["grep", (exit) => ({ isError: exit >= 2, message: exit === 1 ? "No matches found" : void 0 })],
|
|
909
|
+
["rg", (exit) => ({ isError: exit >= 2, message: exit === 1 ? "No matches found" : void 0 })],
|
|
910
|
+
// diff: 0 = identical, 1 = differ, ≥2 = error.
|
|
911
|
+
["diff", (exit) => ({ isError: exit >= 2, message: exit === 1 ? "Files differ" : void 0 })],
|
|
912
|
+
// find: 0 = ok, 1 = some dirs inaccessible (warning), ≥2 = error.
|
|
913
|
+
["find", (exit) => ({ isError: exit >= 2, message: exit === 1 ? "Some directories were inaccessible" : void 0 })],
|
|
914
|
+
// test / [: 0 = condition true, 1 = condition false, ≥2 = error.
|
|
915
|
+
["test", (exit) => ({ isError: exit >= 2, message: exit === 1 ? "Condition is false" : void 0 })],
|
|
916
|
+
["[", (exit) => ({ isError: exit >= 2, message: exit === 1 ? "Condition is false" : void 0 })]
|
|
917
|
+
]);
|
|
918
|
+
function interpretShellResult(command, exitCode) {
|
|
919
|
+
const base = extractTrailingCommand(command);
|
|
920
|
+
const semantic = COMMAND_SEMANTICS.get(base) ?? DEFAULT_SEMANTIC;
|
|
921
|
+
return semantic(exitCode);
|
|
922
|
+
}
|
|
923
|
+
function extractTrailingCommand(command) {
|
|
924
|
+
const segments = command.split(/\|\||&&|[;|\n]/);
|
|
925
|
+
const last = segments[segments.length - 1]?.trim() ?? command;
|
|
926
|
+
const tokens = last.split(/\s+/).filter((t) => !/^[A-Z_]\w*=/i.test(t));
|
|
927
|
+
return tokens[0] ?? "";
|
|
928
|
+
}
|
|
929
|
+
|
|
930
|
+
// src/tools/shell.ts
|
|
670
931
|
var DEFAULT_MAX_OUTPUT_BYTES = 8192;
|
|
671
932
|
var shell = {
|
|
672
933
|
spec: {
|
|
@@ -686,10 +947,19 @@ var shell = {
|
|
|
686
947
|
const execOpts = {};
|
|
687
948
|
if (typeof timeout === "number" && Number.isFinite(timeout) && timeout > 0)
|
|
688
949
|
execOpts.timeout = Math.max(1, Math.ceil(timeout / 1e3));
|
|
689
|
-
const
|
|
950
|
+
const cmd = command;
|
|
951
|
+
const result = await ctx.execution.exec(ctx.handle, cmd, execOpts);
|
|
690
952
|
const cap = normalizeCap(maxOutputBytes);
|
|
953
|
+
const semantic = interpretShellResult(cmd, result.exitCode);
|
|
691
954
|
if (result.exitCode === 0)
|
|
692
955
|
return truncateTail(result.stdout || "(no output)", cap);
|
|
956
|
+
if (!semantic.isError) {
|
|
957
|
+
const body = (result.stdout || result.stderr || "").trim();
|
|
958
|
+
const tail = truncateTail(body, cap);
|
|
959
|
+
const footer = semantic.message ? `
|
|
960
|
+
(${semantic.message})` : "";
|
|
961
|
+
return tail.length > 0 ? `${tail}${footer}` : semantic.message ?? "(no output)";
|
|
962
|
+
}
|
|
693
963
|
const combined = `${result.stdout}
|
|
694
964
|
${result.stderr}`.trim();
|
|
695
965
|
return `Exit code ${result.exitCode}
|
|
@@ -706,21 +976,21 @@ function normalizeCap(value) {
|
|
|
706
976
|
function truncateTail(text, cap) {
|
|
707
977
|
if (cap === 0)
|
|
708
978
|
return text;
|
|
709
|
-
const totalBytes =
|
|
979
|
+
const totalBytes = Buffer3.byteLength(text);
|
|
710
980
|
if (totalBytes <= cap)
|
|
711
981
|
return text;
|
|
712
982
|
let bytes = 0;
|
|
713
983
|
let charIdx = text.length;
|
|
714
984
|
while (charIdx > 0) {
|
|
715
985
|
const ch = text[charIdx - 1];
|
|
716
|
-
const chBytes =
|
|
986
|
+
const chBytes = Buffer3.byteLength(ch);
|
|
717
987
|
if (bytes + chBytes > cap)
|
|
718
988
|
break;
|
|
719
989
|
bytes += chBytes;
|
|
720
990
|
charIdx--;
|
|
721
991
|
}
|
|
722
992
|
const tail = text.slice(charIdx);
|
|
723
|
-
const droppedBytes = totalBytes -
|
|
993
|
+
const droppedBytes = totalBytes - Buffer3.byteLength(tail);
|
|
724
994
|
return `\u2026(${droppedBytes} bytes truncated from head)\u2026
|
|
725
995
|
${tail}`;
|
|
726
996
|
}
|
|
@@ -1020,6 +1290,59 @@ function rewriteMessagesToWire(messages, maps) {
|
|
|
1020
1290
|
return messages.map((msg) => ({ ...msg, content: rewriteContentToWire(msg.content, maps) }));
|
|
1021
1291
|
}
|
|
1022
1292
|
|
|
1293
|
+
// src/dedup-tools.ts
|
|
1294
|
+
function installDedupToolsGate(hooks, getDedupTools, getSession) {
|
|
1295
|
+
const pending = /* @__PURE__ */ new Map();
|
|
1296
|
+
function pendingKey(callId, name) {
|
|
1297
|
+
return `${callId}::${name}`;
|
|
1298
|
+
}
|
|
1299
|
+
function gateHandler(ctx) {
|
|
1300
|
+
if (ctx.block || ctx.result !== void 0)
|
|
1301
|
+
return;
|
|
1302
|
+
const dedupTools = getDedupTools();
|
|
1303
|
+
const hasher = dedupTools?.[ctx.name];
|
|
1304
|
+
if (!hasher)
|
|
1305
|
+
return;
|
|
1306
|
+
const session = getSession();
|
|
1307
|
+
const state = getToolDedupState(session);
|
|
1308
|
+
if (!state)
|
|
1309
|
+
return;
|
|
1310
|
+
let hash;
|
|
1311
|
+
try {
|
|
1312
|
+
hash = hasher(ctx.input);
|
|
1313
|
+
} catch {
|
|
1314
|
+
return;
|
|
1315
|
+
}
|
|
1316
|
+
if (typeof hash !== "string" || hash.length === 0)
|
|
1317
|
+
return;
|
|
1318
|
+
const prior = state.get(ctx.name);
|
|
1319
|
+
if (prior && prior.hash === hash) {
|
|
1320
|
+
ctx.result = prior.result;
|
|
1321
|
+
return;
|
|
1322
|
+
}
|
|
1323
|
+
pending.set(pendingKey(ctx.callId, ctx.name), hash);
|
|
1324
|
+
}
|
|
1325
|
+
function afterHandler(ctx) {
|
|
1326
|
+
const key = pendingKey(ctx.callId, ctx.name);
|
|
1327
|
+
const hash = pending.get(key);
|
|
1328
|
+
if (hash === void 0)
|
|
1329
|
+
return;
|
|
1330
|
+
pending.delete(key);
|
|
1331
|
+
const session = getSession();
|
|
1332
|
+
const state = getToolDedupState(session);
|
|
1333
|
+
if (!state)
|
|
1334
|
+
return;
|
|
1335
|
+
state.set(ctx.name, { hash, result: ctx.result });
|
|
1336
|
+
}
|
|
1337
|
+
const unregisterGate = hooks.hook("tool:gate", gateHandler);
|
|
1338
|
+
const unregisterAfter = hooks.hook("tool:after", afterHandler);
|
|
1339
|
+
return function uninstall() {
|
|
1340
|
+
unregisterGate();
|
|
1341
|
+
unregisterAfter();
|
|
1342
|
+
pending.clear();
|
|
1343
|
+
};
|
|
1344
|
+
}
|
|
1345
|
+
|
|
1023
1346
|
// src/tools/validation.ts
|
|
1024
1347
|
var TRUE_STRINGS = /* @__PURE__ */ new Set(["true", "True", "TRUE", "1", "yes", "Yes", "YES"]);
|
|
1025
1348
|
var FALSE_STRINGS = /* @__PURE__ */ new Set(["false", "False", "FALSE", "0", "no", "No", "NO"]);
|
|
@@ -1175,6 +1498,24 @@ function formatValue(value) {
|
|
|
1175
1498
|
|
|
1176
1499
|
// src/loop.ts
|
|
1177
1500
|
var IMAGE_OMITTED_MARKER = "[image omitted \u2014 model does not support vision]";
|
|
1501
|
+
function applyThinkingDecay(baseBudget, decay, turn) {
|
|
1502
|
+
if (typeof baseBudget !== "number" || baseBudget <= 0)
|
|
1503
|
+
return baseBudget;
|
|
1504
|
+
if (!decay)
|
|
1505
|
+
return baseBudget;
|
|
1506
|
+
let raw;
|
|
1507
|
+
if (typeof decay === "function") {
|
|
1508
|
+
raw = decay(turn, baseBudget);
|
|
1509
|
+
} else {
|
|
1510
|
+
if (turn <= decay.afterTurn)
|
|
1511
|
+
return baseBudget;
|
|
1512
|
+
const k = turn - decay.afterTurn;
|
|
1513
|
+
raw = Math.max(decay.floor, baseBudget * decay.factor ** k);
|
|
1514
|
+
}
|
|
1515
|
+
if (Number.isNaN(raw) || raw <= 0)
|
|
1516
|
+
return 0;
|
|
1517
|
+
return Math.round(Math.min(baseBudget, raw));
|
|
1518
|
+
}
|
|
1178
1519
|
function turnsToMessages(turns) {
|
|
1179
1520
|
return turns.filter((t) => t.role !== "system").map((t) => ({ role: t.role, content: t.content }));
|
|
1180
1521
|
}
|
|
@@ -1330,6 +1671,7 @@ async function executeTurn(ctx, turn) {
|
|
|
1330
1671
|
const keep = typeof ctx.compactKeepTurns === "number" && ctx.compactKeepTurns >= 0 ? ctx.compactKeepTurns : 4;
|
|
1331
1672
|
sanitizedMessages = applyTailCompaction(sanitizedMessages, threshold, keep);
|
|
1332
1673
|
}
|
|
1674
|
+
const effectiveThinkingBudget = applyThinkingDecay(ctx.thinkingBudget, ctx.thinkingDecay, turn);
|
|
1333
1675
|
const streamOptions = {
|
|
1334
1676
|
model: ctx.model,
|
|
1335
1677
|
system: ctx.system,
|
|
@@ -1337,13 +1679,22 @@ async function executeTurn(ctx, turn) {
|
|
|
1337
1679
|
messages: sanitizedMessages,
|
|
1338
1680
|
maxTokens: ctx.maxTokens ?? 16384,
|
|
1339
1681
|
thinking: ctx.thinking,
|
|
1340
|
-
thinkingBudget:
|
|
1682
|
+
thinkingBudget: effectiveThinkingBudget,
|
|
1341
1683
|
cache: ctx.cache ?? true,
|
|
1342
1684
|
signal: ctx.signal
|
|
1343
1685
|
};
|
|
1344
1686
|
const transformCtx = { messages: streamOptions.messages };
|
|
1345
1687
|
await ctx.hooks.callHook("context:transform", transformCtx);
|
|
1346
1688
|
streamOptions.messages = transformCtx.messages;
|
|
1689
|
+
const systemCtx = {
|
|
1690
|
+
system: streamOptions.system,
|
|
1691
|
+
messages: streamOptions.messages,
|
|
1692
|
+
turn,
|
|
1693
|
+
turnId,
|
|
1694
|
+
...ctx.session ? { session: ctx.session } : {}
|
|
1695
|
+
};
|
|
1696
|
+
await ctx.hooks.callHook("system:transform", systemCtx);
|
|
1697
|
+
streamOptions.system = systemCtx.system;
|
|
1347
1698
|
await ctx.hooks.callHook("turn:before", { turn, turnId, options: streamOptions });
|
|
1348
1699
|
let currentText = "";
|
|
1349
1700
|
let currentThinking = "";
|
|
@@ -1376,7 +1727,13 @@ async function executeTurn(ctx, turn) {
|
|
|
1376
1727
|
createdAt: Date.now()
|
|
1377
1728
|
};
|
|
1378
1729
|
ctx.turns.push(errorTurn);
|
|
1379
|
-
await ctx.hooks.callHook("turn:after", {
|
|
1730
|
+
await ctx.hooks.callHook("turn:after", {
|
|
1731
|
+
turn,
|
|
1732
|
+
turnId,
|
|
1733
|
+
usage: errorUsage,
|
|
1734
|
+
message: errorTurn,
|
|
1735
|
+
toolCounts: { turn: Object.freeze({}), run: Object.freeze({ ...ctx.runToolCounts }) }
|
|
1736
|
+
});
|
|
1380
1737
|
throw wrapProviderError(err, ctx);
|
|
1381
1738
|
}
|
|
1382
1739
|
if (currentText) {
|
|
@@ -1399,7 +1756,16 @@ async function executeTurn(ctx, turn) {
|
|
|
1399
1756
|
createdAt: Date.now()
|
|
1400
1757
|
};
|
|
1401
1758
|
ctx.turns.push(assistantTurn);
|
|
1402
|
-
|
|
1759
|
+
const turnCounts = {};
|
|
1760
|
+
for (const tc of canonicalToolCalls)
|
|
1761
|
+
turnCounts[tc.name] = (turnCounts[tc.name] ?? 0) + 1;
|
|
1762
|
+
await ctx.hooks.callHook("turn:after", {
|
|
1763
|
+
turn,
|
|
1764
|
+
turnId,
|
|
1765
|
+
usage: result.usage,
|
|
1766
|
+
message: assistantTurn,
|
|
1767
|
+
toolCounts: { turn: Object.freeze(turnCounts), run: Object.freeze({ ...ctx.runToolCounts }) }
|
|
1768
|
+
});
|
|
1403
1769
|
if (result.done) {
|
|
1404
1770
|
if (ctx.schema && !ctx.signal.aborted) {
|
|
1405
1771
|
const outputSpec = {
|
|
@@ -1456,6 +1822,17 @@ async function executeTurn(ctx, turn) {
|
|
|
1456
1822
|
}
|
|
1457
1823
|
return { ended: true, turnId, usage: result.usage };
|
|
1458
1824
|
}
|
|
1825
|
+
if (canonicalToolCalls.length === 0 && result.usage.finishReason === "pause") {
|
|
1826
|
+
const continueMsg = ctx.provider.userMessage("Please continue.");
|
|
1827
|
+
ctx.turns.push({
|
|
1828
|
+
id: await ctx.generateTurnId(),
|
|
1829
|
+
runId: ctx.runId,
|
|
1830
|
+
role: continueMsg.role,
|
|
1831
|
+
content: continueMsg.content,
|
|
1832
|
+
createdAt: Date.now()
|
|
1833
|
+
});
|
|
1834
|
+
return { ended: false, turnId, usage: result.usage };
|
|
1835
|
+
}
|
|
1459
1836
|
const toolResults = ctx.toolExecution === "parallel" ? await executeToolsParallel(ctx, canonicalToolCalls, turnId) : await executeToolsSequential(ctx, canonicalToolCalls, turnId);
|
|
1460
1837
|
const toolResultMsg = ctx.provider.toolResultsMessage(toolResults);
|
|
1461
1838
|
ctx.turns.push({
|
|
@@ -1501,6 +1878,7 @@ async function executeSingleTool(ctx, call, turnId) {
|
|
|
1501
1878
|
const toolDef = ctx.tools[call.name];
|
|
1502
1879
|
const callId = call.id;
|
|
1503
1880
|
const displayName = toWireName(call.name, ctx.aliasMaps);
|
|
1881
|
+
const runToolCounts = Object.freeze({ ...ctx.runToolCounts });
|
|
1504
1882
|
const gateCtx = {
|
|
1505
1883
|
turnId,
|
|
1506
1884
|
callId,
|
|
@@ -1508,12 +1886,27 @@ async function executeSingleTool(ctx, call, turnId) {
|
|
|
1508
1886
|
displayName,
|
|
1509
1887
|
input: call.input,
|
|
1510
1888
|
block: false,
|
|
1511
|
-
reason: "Tool execution was blocked"
|
|
1889
|
+
reason: "Tool execution was blocked",
|
|
1890
|
+
runToolCounts
|
|
1512
1891
|
};
|
|
1513
1892
|
await ctx.hooks.callHook("tool:gate", gateCtx);
|
|
1514
1893
|
if (gateCtx.block) {
|
|
1515
1894
|
return { result: { id: callId, content: `Blocked: ${gateCtx.reason}` } };
|
|
1516
1895
|
}
|
|
1896
|
+
ctx.runToolCounts[call.name] = (ctx.runToolCounts[call.name] ?? 0) + 1;
|
|
1897
|
+
if (gateCtx.result !== void 0) {
|
|
1898
|
+
const substitute = await emitToolResult(ctx, {
|
|
1899
|
+
turnId,
|
|
1900
|
+
callId,
|
|
1901
|
+
name: call.name,
|
|
1902
|
+
displayName,
|
|
1903
|
+
input: gateCtx.input,
|
|
1904
|
+
output: gateCtx.result,
|
|
1905
|
+
isError: false,
|
|
1906
|
+
runToolCounts
|
|
1907
|
+
});
|
|
1908
|
+
return { result: { id: callId, content: substitute } };
|
|
1909
|
+
}
|
|
1517
1910
|
let effectiveInput = gateCtx.input;
|
|
1518
1911
|
if (!toolDef) {
|
|
1519
1912
|
const unknownCtx = {
|
|
@@ -1571,6 +1964,7 @@ async function executeSingleTool(ctx, call, turnId) {
|
|
|
1571
1964
|
name: call.name,
|
|
1572
1965
|
displayName,
|
|
1573
1966
|
input: effectiveInput,
|
|
1967
|
+
runToolCounts,
|
|
1574
1968
|
...coercions ? { coercions } : {}
|
|
1575
1969
|
});
|
|
1576
1970
|
let output;
|
|
@@ -1610,12 +2004,29 @@ async function executeSingleTool(ctx, call, turnId) {
|
|
|
1610
2004
|
output = errorCtx.result ?? `Tool error: ${error.message}`;
|
|
1611
2005
|
isError = true;
|
|
1612
2006
|
}
|
|
1613
|
-
const
|
|
2007
|
+
const finalOutput = await emitToolResult(ctx, {
|
|
1614
2008
|
turnId,
|
|
1615
2009
|
callId,
|
|
1616
2010
|
name: call.name,
|
|
1617
2011
|
displayName,
|
|
1618
2012
|
input: effectiveInput,
|
|
2013
|
+
output,
|
|
2014
|
+
isError,
|
|
2015
|
+
runToolCounts,
|
|
2016
|
+
...coercions ? { coercions } : {}
|
|
2017
|
+
});
|
|
2018
|
+
return { result: { id: callId, content: finalOutput } };
|
|
2019
|
+
}
|
|
2020
|
+
async function emitToolResult(ctx, params) {
|
|
2021
|
+
const { turnId, callId, name, displayName, input, runToolCounts, coercions } = params;
|
|
2022
|
+
let output = params.output;
|
|
2023
|
+
let isError = params.isError;
|
|
2024
|
+
const transformCtx = {
|
|
2025
|
+
turnId,
|
|
2026
|
+
callId,
|
|
2027
|
+
name,
|
|
2028
|
+
displayName,
|
|
2029
|
+
input,
|
|
1619
2030
|
result: output,
|
|
1620
2031
|
isError,
|
|
1621
2032
|
outputBytes: toolOutputByteLength(output),
|
|
@@ -1628,14 +2039,15 @@ async function executeSingleTool(ctx, call, turnId) {
|
|
|
1628
2039
|
await ctx.hooks.callHook("tool:after", {
|
|
1629
2040
|
turnId,
|
|
1630
2041
|
callId,
|
|
1631
|
-
name
|
|
2042
|
+
name,
|
|
1632
2043
|
displayName,
|
|
1633
|
-
input
|
|
2044
|
+
input,
|
|
1634
2045
|
result: output,
|
|
1635
2046
|
outputBytes: toolOutputByteLength(output),
|
|
2047
|
+
runToolCounts,
|
|
1636
2048
|
...coercions ? { coercions } : {}
|
|
1637
2049
|
});
|
|
1638
|
-
return
|
|
2050
|
+
return output;
|
|
1639
2051
|
}
|
|
1640
2052
|
async function executeToolsSequential(ctx, toolCalls, turnId) {
|
|
1641
2053
|
const results = [];
|
|
@@ -1744,6 +2156,81 @@ function buildPromptMessage(provider, parts) {
|
|
|
1744
2156
|
return defaultPromptMessage(parts);
|
|
1745
2157
|
}
|
|
1746
2158
|
|
|
2159
|
+
// src/tool-budgets.ts
|
|
2160
|
+
function installToolBudgetsGate(hooks, getToolBudgets, enqueueSteer) {
|
|
2161
|
+
const steeredOnce = /* @__PURE__ */ new Set();
|
|
2162
|
+
const approvedCounts = {};
|
|
2163
|
+
async function gateHandler(ctx) {
|
|
2164
|
+
if (ctx.block || ctx.result !== void 0)
|
|
2165
|
+
return;
|
|
2166
|
+
const toolBudgets = getToolBudgets();
|
|
2167
|
+
const budget = toolBudgets?.[ctx.name];
|
|
2168
|
+
if (!budget)
|
|
2169
|
+
return;
|
|
2170
|
+
const max = budget.max;
|
|
2171
|
+
if (typeof max !== "number" || max <= 0)
|
|
2172
|
+
return;
|
|
2173
|
+
const count = approvedCounts[ctx.name] ?? 0;
|
|
2174
|
+
if (count < max) {
|
|
2175
|
+
approvedCounts[ctx.name] = count + 1;
|
|
2176
|
+
return;
|
|
2177
|
+
}
|
|
2178
|
+
const onExceed = budget.onExceed ?? "steer";
|
|
2179
|
+
let mode;
|
|
2180
|
+
let message;
|
|
2181
|
+
if (typeof onExceed === "function") {
|
|
2182
|
+
try {
|
|
2183
|
+
const out = onExceed({ tool: ctx.name, count, max });
|
|
2184
|
+
mode = out.mode;
|
|
2185
|
+
message = out.message;
|
|
2186
|
+
} catch {
|
|
2187
|
+
mode = "steer";
|
|
2188
|
+
message = defaultSteerMessage(ctx.name, count, max);
|
|
2189
|
+
}
|
|
2190
|
+
} else if (onExceed === "block") {
|
|
2191
|
+
mode = "block";
|
|
2192
|
+
message = defaultBlockMessage(ctx.name, max);
|
|
2193
|
+
} else {
|
|
2194
|
+
mode = "steer";
|
|
2195
|
+
message = defaultSteerMessage(ctx.name, count, max);
|
|
2196
|
+
}
|
|
2197
|
+
if (mode === "block") {
|
|
2198
|
+
ctx.block = true;
|
|
2199
|
+
ctx.reason = message;
|
|
2200
|
+
await hooks.callHook("tool-budget:exceeded", {
|
|
2201
|
+
tool: ctx.name,
|
|
2202
|
+
count,
|
|
2203
|
+
max,
|
|
2204
|
+
turnId: ctx.turnId,
|
|
2205
|
+
mode: "block"
|
|
2206
|
+
});
|
|
2207
|
+
return;
|
|
2208
|
+
}
|
|
2209
|
+
if (!steeredOnce.has(ctx.name)) {
|
|
2210
|
+
steeredOnce.add(ctx.name);
|
|
2211
|
+
enqueueSteer(message);
|
|
2212
|
+
await hooks.callHook("tool-budget:exceeded", {
|
|
2213
|
+
tool: ctx.name,
|
|
2214
|
+
count,
|
|
2215
|
+
max,
|
|
2216
|
+
turnId: ctx.turnId,
|
|
2217
|
+
mode: "steer"
|
|
2218
|
+
});
|
|
2219
|
+
}
|
|
2220
|
+
}
|
|
2221
|
+
const unregister = hooks.hook("tool:gate", gateHandler);
|
|
2222
|
+
return function uninstall() {
|
|
2223
|
+
unregister();
|
|
2224
|
+
steeredOnce.clear();
|
|
2225
|
+
};
|
|
2226
|
+
}
|
|
2227
|
+
function defaultSteerMessage(tool, count, max) {
|
|
2228
|
+
return `[Tool budget reached: '${tool}' has been called ${count} times this run (cap: ${max}). Avoid calling it again unless strictly necessary; commit to a result and move on.]`;
|
|
2229
|
+
}
|
|
2230
|
+
function defaultBlockMessage(tool, max) {
|
|
2231
|
+
return `Tool '${tool}' has reached its per-run budget of ${max} calls; further invocations are refused.`;
|
|
2232
|
+
}
|
|
2233
|
+
|
|
1747
2234
|
// src/agent.ts
|
|
1748
2235
|
var HOOK_EVENT_NAMES = [
|
|
1749
2236
|
"system:before",
|
|
@@ -1762,6 +2249,7 @@ var HOOK_EVENT_NAMES = [
|
|
|
1762
2249
|
"validation:reject",
|
|
1763
2250
|
"validation:coerce",
|
|
1764
2251
|
"context:transform",
|
|
2252
|
+
"system:transform",
|
|
1765
2253
|
"steer:inject",
|
|
1766
2254
|
"spawn:before",
|
|
1767
2255
|
"spawn:complete",
|
|
@@ -1790,6 +2278,7 @@ var HOOK_EVENT_NAMES = [
|
|
|
1790
2278
|
"usage",
|
|
1791
2279
|
"output",
|
|
1792
2280
|
"budget:exceeded",
|
|
2281
|
+
"tool-budget:exceeded",
|
|
1793
2282
|
"agent:abort",
|
|
1794
2283
|
"agent:done",
|
|
1795
2284
|
"session:start",
|
|
@@ -1813,7 +2302,12 @@ function resolveBehavior(agentBehavior, runBehavior) {
|
|
|
1813
2302
|
toolOutputBudget: runBehavior?.toolOutputBudget ?? agentBehavior?.toolOutputBudget,
|
|
1814
2303
|
compactStrategy: runBehavior?.compactStrategy ?? agentBehavior?.compactStrategy ?? "off",
|
|
1815
2304
|
compactThreshold: runBehavior?.compactThreshold ?? agentBehavior?.compactThreshold,
|
|
1816
|
-
compactKeepTurns: runBehavior?.compactKeepTurns ?? agentBehavior?.compactKeepTurns
|
|
2305
|
+
compactKeepTurns: runBehavior?.compactKeepTurns ?? agentBehavior?.compactKeepTurns,
|
|
2306
|
+
thinkingDecay: runBehavior?.thinkingDecay ?? agentBehavior?.thinkingDecay,
|
|
2307
|
+
dedupReads: runBehavior?.dedupReads ?? agentBehavior?.dedupReads,
|
|
2308
|
+
dedupTools: runBehavior?.dedupTools ?? agentBehavior?.dedupTools,
|
|
2309
|
+
requireReadBeforeEdit: runBehavior?.requireReadBeforeEdit ?? agentBehavior?.requireReadBeforeEdit,
|
|
2310
|
+
toolBudgets: runBehavior?.toolBudgets ?? agentBehavior?.toolBudgets
|
|
1817
2311
|
};
|
|
1818
2312
|
}
|
|
1819
2313
|
function createAgent({ provider, name: agentName, system: agentSystem, tools: agentTools, toolAliases, behavior: agentBehavior, execution, mcpServers, session, skills: agentSkills, mcpConnector, eager }) {
|
|
@@ -1936,7 +2430,8 @@ function createAgent({ provider, name: agentName, system: agentSystem, tools: ag
|
|
|
1936
2430
|
}
|
|
1937
2431
|
const thinking = options.thinking ?? "off";
|
|
1938
2432
|
const model = options.model ?? provider.meta.defaultModel;
|
|
1939
|
-
const
|
|
2433
|
+
const resolvedBehavior = resolveBehavior(agentBehavior, options.behavior);
|
|
2434
|
+
const { toolExecution, maxTurns, maxTokens, thinkingBudget, schema, cache, toolOutputBudget, compactStrategy, compactThreshold, compactKeepTurns, thinkingDecay, dedupTools, toolBudgets } = resolvedBehavior;
|
|
1940
2435
|
let system = options.system || agentSystem || "You are a helpful assistant.";
|
|
1941
2436
|
if (skillsCatalog) {
|
|
1942
2437
|
system = `${system}
|
|
@@ -2036,6 +2531,16 @@ ${skillsCatalog}`;
|
|
|
2036
2531
|
await hooks.callHook("session:end", { sessionId: session.id, runId, status, turnRange: [runTurnStart, turns.length - 1] });
|
|
2037
2532
|
}
|
|
2038
2533
|
const uninstallAllowedToolsGate = installAllowedToolsGate(hooks, skillActivationState);
|
|
2534
|
+
const uninstallToolBudgets = installToolBudgetsGate(
|
|
2535
|
+
hooks,
|
|
2536
|
+
() => toolBudgets,
|
|
2537
|
+
(msg) => steeringQueue.push(msg)
|
|
2538
|
+
);
|
|
2539
|
+
const uninstallDedupTools = installDedupToolsGate(
|
|
2540
|
+
hooks,
|
|
2541
|
+
() => dedupTools,
|
|
2542
|
+
() => session ?? void 0
|
|
2543
|
+
);
|
|
2039
2544
|
const runStartMs = Date.now();
|
|
2040
2545
|
const runDepth = typeof options.depth === "number" ? options.depth : 0;
|
|
2041
2546
|
try {
|
|
@@ -2048,7 +2553,10 @@ ${skillsCatalog}`;
|
|
|
2048
2553
|
agentToolAliases: toolAliases,
|
|
2049
2554
|
agentMcpServers: mcpServers,
|
|
2050
2555
|
agentSkills,
|
|
2051
|
-
|
|
2556
|
+
// Forward the resolved view (agent + run merged) so per-run overrides
|
|
2557
|
+
// of `dedupReads` / `requireReadBeforeEdit` / etc. are visible to
|
|
2558
|
+
// tools via `ToolContext.behavior`.
|
|
2559
|
+
agentBehavior: resolvedBehavior,
|
|
2052
2560
|
tools,
|
|
2053
2561
|
formattedTools,
|
|
2054
2562
|
aliasMaps,
|
|
@@ -2075,7 +2583,9 @@ ${skillsCatalog}`;
|
|
|
2075
2583
|
compactStrategy,
|
|
2076
2584
|
compactThreshold,
|
|
2077
2585
|
compactKeepTurns,
|
|
2078
|
-
|
|
2586
|
+
...thinkingDecay !== void 0 ? { thinkingDecay } : {},
|
|
2587
|
+
runStartMs,
|
|
2588
|
+
runToolCounts: {}
|
|
2079
2589
|
});
|
|
2080
2590
|
const finalStats = {
|
|
2081
2591
|
...stats,
|
|
@@ -2117,6 +2627,8 @@ ${skillsCatalog}`;
|
|
|
2117
2627
|
} finally {
|
|
2118
2628
|
await deactivateAllSkills();
|
|
2119
2629
|
uninstallAllowedToolsGate();
|
|
2630
|
+
uninstallDedupTools();
|
|
2631
|
+
uninstallToolBudgets();
|
|
2120
2632
|
unregisterSpawnHook();
|
|
2121
2633
|
unregisterSessionSync?.();
|
|
2122
2634
|
for (const unregister of perRunUnregisters)
|
|
@@ -2519,7 +3031,7 @@ function createSpawnTool(options = {}) {
|
|
|
2519
3031
|
var spawn = createSpawnTool();
|
|
2520
3032
|
|
|
2521
3033
|
// src/tools/write-file.ts
|
|
2522
|
-
import { Buffer as
|
|
3034
|
+
import { Buffer as Buffer4 } from "buffer";
|
|
2523
3035
|
var writeFile = {
|
|
2524
3036
|
spec: {
|
|
2525
3037
|
name: "write_file",
|
|
@@ -2541,7 +3053,7 @@ var writeFile = {
|
|
|
2541
3053
|
existing = await ctx.execution.readFile(ctx.handle, targetPath);
|
|
2542
3054
|
} catch {
|
|
2543
3055
|
}
|
|
2544
|
-
const bytes =
|
|
3056
|
+
const bytes = Buffer4.byteLength(targetContent);
|
|
2545
3057
|
if (existing === targetContent)
|
|
2546
3058
|
return `No change needed: ${targetPath} already at target state (${bytes} bytes).`;
|
|
2547
3059
|
await ctx.execution.writeFile(ctx.handle, targetPath, targetContent);
|