zidane 3.1.1 → 3.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +110 -7
- package/dist/{agent-Cq009tbG.d.ts → agent-CE2jhpNE.d.ts} +275 -2
- package/dist/{chunk-EBSFBIP3.js → chunk-6JIVVEQQ.js} +338 -80
- package/dist/{chunk-R74LQKAM.js → chunk-7H34OFDA.js} +26 -0
- package/dist/{chunk-3DUWP7YU.js → chunk-AUBXCLUC.js} +6 -1
- package/dist/{chunk-ATMVSCGJ.js → chunk-HPTCF3EX.js} +1 -1
- package/dist/{chunk-TPXPVEH6.js → chunk-J4ZOSNSH.js} +16 -3
- package/dist/{chunk-VF4A7HAC.js → chunk-QX7TDFD4.js} +27 -13
- package/dist/{chunk-IUBBVF53.js → chunk-UD25QF3H.js} +66 -8
- package/dist/{chunk-BW3WTFIR.js → chunk-YPU6KVL6.js} +1 -1
- package/dist/contexts.js +1 -1
- package/dist/index.d.ts +4 -4
- package/dist/index.js +8 -8
- package/dist/mcp.d.ts +1 -1
- package/dist/mcp.js +1 -1
- package/dist/presets.d.ts +1 -1
- package/dist/presets.js +5 -5
- package/dist/providers.d.ts +1 -1
- package/dist/providers.js +2 -2
- package/dist/session/sqlite.d.ts +1 -1
- package/dist/session/sqlite.js +6 -7
- package/dist/session.d.ts +1 -1
- package/dist/session.js +1 -1
- package/dist/{skills-use-Bi6Dklye.d.ts → skills-use-CqOKEN56.d.ts} +1 -1
- package/dist/skills.d.ts +12 -2
- package/dist/skills.js +2 -2
- package/dist/tools.d.ts +4 -4
- package/dist/tools.js +4 -4
- package/dist/types.d.ts +2 -2
- package/dist/{validation-BeQD94ft.d.ts → validation-DlIURVGV.d.ts} +1 -1
- package/package.json +1 -1
|
@@ -3,15 +3,15 @@ import {
|
|
|
3
3
|
createSkillActivationState,
|
|
4
4
|
installAllowedToolsGate,
|
|
5
5
|
interpolateShellCommands,
|
|
6
|
-
|
|
6
|
+
resolveSkillsWithCleanup,
|
|
7
7
|
validateResourcePath
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-J4ZOSNSH.js";
|
|
9
9
|
import {
|
|
10
10
|
createProcessContext
|
|
11
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-UD25QF3H.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";
|
|
@@ -197,6 +197,17 @@ function getReadState(session) {
|
|
|
197
197
|
}
|
|
198
198
|
return map;
|
|
199
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
|
+
}
|
|
200
211
|
function hashContent(text) {
|
|
201
212
|
let h = 2166136261;
|
|
202
213
|
for (let i = 0; i < text.length; i++) {
|
|
@@ -356,6 +367,18 @@ var glob = {
|
|
|
356
367
|
}
|
|
357
368
|
};
|
|
358
369
|
|
|
370
|
+
// src/tools/shell-quote.ts
|
|
371
|
+
var SAFE_TOKEN_RE = /^[\w@%+=:,./-]+$/;
|
|
372
|
+
var SINGLE_QUOTE_RE = /'/g;
|
|
373
|
+
function shellQuote(arg) {
|
|
374
|
+
if (SAFE_TOKEN_RE.test(arg))
|
|
375
|
+
return arg;
|
|
376
|
+
return `'${arg.replace(SINGLE_QUOTE_RE, "'\\''")}'`;
|
|
377
|
+
}
|
|
378
|
+
function alwaysQuote(arg) {
|
|
379
|
+
return `'${arg.replace(SINGLE_QUOTE_RE, "'\\''")}'`;
|
|
380
|
+
}
|
|
381
|
+
|
|
359
382
|
// src/tools/grep.ts
|
|
360
383
|
var DEFAULT_HEAD_LIMIT = 250;
|
|
361
384
|
var DEFAULT_OUTPUT_MODE = "files_with_matches";
|
|
@@ -431,11 +454,6 @@ async function runViaRipgrep(input, ctx) {
|
|
|
431
454
|
}
|
|
432
455
|
return formatPaginated(result.stdout, input);
|
|
433
456
|
}
|
|
434
|
-
function shellQuote(arg) {
|
|
435
|
-
if (/^[\w@%+=:,./-]+$/.test(arg))
|
|
436
|
-
return arg;
|
|
437
|
-
return `'${arg.replace(/'/g, "'\\''")}'`;
|
|
438
|
-
}
|
|
439
457
|
async function runInProcess(input, ctx) {
|
|
440
458
|
const mode = input.output_mode ?? DEFAULT_OUTPUT_MODE;
|
|
441
459
|
const flags = `${input["-i"] ? "i" : ""}${input.multiline ? "s" : ""}${mode !== "content" ? "" : "g"}`;
|
|
@@ -684,6 +702,33 @@ var multiEdit = {
|
|
|
684
702
|
// src/tools/read-file.ts
|
|
685
703
|
import { Buffer as Buffer2 } from "buffer";
|
|
686
704
|
|
|
705
|
+
// src/tools/binary-detect.ts
|
|
706
|
+
var SNIFF_BYTES = 8192;
|
|
707
|
+
var REPLACEMENT_RATIO_THRESHOLD = 0.01;
|
|
708
|
+
var REPLACEMENT_MIN_COUNT = 5;
|
|
709
|
+
function containsNullByte(text, sniffBytes = SNIFF_BYTES) {
|
|
710
|
+
const sample = text.length > sniffBytes ? text.slice(0, sniffBytes) : text;
|
|
711
|
+
for (let i = 0; i < sample.length; i++) {
|
|
712
|
+
if (sample.charCodeAt(i) === 0)
|
|
713
|
+
return true;
|
|
714
|
+
}
|
|
715
|
+
return false;
|
|
716
|
+
}
|
|
717
|
+
function looksBinary(text, sniffBytes = SNIFF_BYTES) {
|
|
718
|
+
const sample = text.length > sniffBytes ? text.slice(0, sniffBytes) : text;
|
|
719
|
+
if (sample.length === 0)
|
|
720
|
+
return false;
|
|
721
|
+
let replacementCount = 0;
|
|
722
|
+
for (let i = 0; i < sample.length; i++) {
|
|
723
|
+
const code = sample.charCodeAt(i);
|
|
724
|
+
if (code === 0)
|
|
725
|
+
return true;
|
|
726
|
+
if (code === 65533)
|
|
727
|
+
replacementCount++;
|
|
728
|
+
}
|
|
729
|
+
return replacementCount >= REPLACEMENT_MIN_COUNT && replacementCount / sample.length > REPLACEMENT_RATIO_THRESHOLD;
|
|
730
|
+
}
|
|
731
|
+
|
|
687
732
|
// src/tools/binary-read.ts
|
|
688
733
|
import { Buffer } from "buffer";
|
|
689
734
|
function imageMediaTypeFor(path) {
|
|
@@ -711,21 +756,27 @@ async function readFileAsBase64(execution, handle, path) {
|
|
|
711
756
|
const b642 = Buffer.from(bytes.buffer, bytes.byteOffset, bytes.byteLength).toString("base64");
|
|
712
757
|
return { base64: b642, byteLength: bytes.byteLength };
|
|
713
758
|
}
|
|
714
|
-
const cmd = `base64 < ${
|
|
759
|
+
const cmd = `base64 < ${alwaysQuote(path)}`;
|
|
715
760
|
const result = await execution.exec(handle, cmd);
|
|
716
761
|
if (result.exitCode !== 0)
|
|
717
762
|
throw new Error(`base64 read failed: ${result.stderr || `exit ${result.exitCode}`}`);
|
|
718
763
|
const b64 = result.stdout.replace(/\s+/g, "");
|
|
719
|
-
return { base64: b64, byteLength:
|
|
764
|
+
return { base64: b64, byteLength: decodedBase64ByteLength(b64) };
|
|
720
765
|
}
|
|
721
|
-
function
|
|
722
|
-
|
|
766
|
+
function decodedBase64ByteLength(b64) {
|
|
767
|
+
if (b64.length === 0)
|
|
768
|
+
return 0;
|
|
769
|
+
let pad = 0;
|
|
770
|
+
if (b64.endsWith("=="))
|
|
771
|
+
pad = 2;
|
|
772
|
+
else if (b64.endsWith("="))
|
|
773
|
+
pad = 1;
|
|
774
|
+
return Math.max(0, b64.length * 3 / 4 - pad);
|
|
723
775
|
}
|
|
724
776
|
|
|
725
777
|
// src/tools/read-file.ts
|
|
726
778
|
var DEFAULT_LINE_LIMIT = 2e3;
|
|
727
779
|
var DEFAULT_BYTE_CAP = 65536;
|
|
728
|
-
var BINARY_PROBE_BYTES = 8e3;
|
|
729
780
|
var DEFAULT_IMAGE_BYTE_CAP = 5 * 1024 * 1024;
|
|
730
781
|
var readFile = {
|
|
731
782
|
spec: {
|
|
@@ -868,21 +919,6 @@ function normalizeInteger(value, fallback) {
|
|
|
868
919
|
return fallback;
|
|
869
920
|
return Math.floor(value);
|
|
870
921
|
}
|
|
871
|
-
var REPLACEMENT_RATIO_THRESHOLD = 0.01;
|
|
872
|
-
var REPLACEMENT_MIN_COUNT = 5;
|
|
873
|
-
function looksBinary(text) {
|
|
874
|
-
const sample = text.length > BINARY_PROBE_BYTES ? text.slice(0, BINARY_PROBE_BYTES) : text;
|
|
875
|
-
if (sample.includes("\0"))
|
|
876
|
-
return true;
|
|
877
|
-
if (sample.length === 0)
|
|
878
|
-
return false;
|
|
879
|
-
let replacementCount = 0;
|
|
880
|
-
for (let i = 0; i < sample.length; i++) {
|
|
881
|
-
if (sample.charCodeAt(i) === 65533)
|
|
882
|
-
replacementCount++;
|
|
883
|
-
}
|
|
884
|
-
return replacementCount >= REPLACEMENT_MIN_COUNT && replacementCount / sample.length > REPLACEMENT_RATIO_THRESHOLD;
|
|
885
|
-
}
|
|
886
922
|
|
|
887
923
|
// src/tools/shell.ts
|
|
888
924
|
import { Buffer as Buffer3 } from "buffer";
|
|
@@ -985,15 +1021,6 @@ ${tail}`;
|
|
|
985
1021
|
}
|
|
986
1022
|
|
|
987
1023
|
// src/tools/skills-read.ts
|
|
988
|
-
var SNIFF_BYTES = 8192;
|
|
989
|
-
function looksBinary2(text) {
|
|
990
|
-
const len = Math.min(text.length, SNIFF_BYTES);
|
|
991
|
-
for (let i = 0; i < len; i++) {
|
|
992
|
-
if (text.charCodeAt(i) === 0)
|
|
993
|
-
return true;
|
|
994
|
-
}
|
|
995
|
-
return false;
|
|
996
|
-
}
|
|
997
1024
|
function createSkillsReadTool(options) {
|
|
998
1025
|
const byName = new Map(options.catalog.map((s) => [s.name, s]));
|
|
999
1026
|
return {
|
|
@@ -1038,7 +1065,7 @@ function createSkillsReadTool(options) {
|
|
|
1038
1065
|
const message = err instanceof Error ? err.message : String(err);
|
|
1039
1066
|
return `Error reading "${relPath}" in skill "${skillName}": ${message}`;
|
|
1040
1067
|
}
|
|
1041
|
-
if (
|
|
1068
|
+
if (containsNullByte(content)) {
|
|
1042
1069
|
return JSON.stringify({
|
|
1043
1070
|
kind: "binary-unsupported",
|
|
1044
1071
|
path: validated.absolutePath,
|
|
@@ -1051,12 +1078,8 @@ function createSkillsReadTool(options) {
|
|
|
1051
1078
|
}
|
|
1052
1079
|
|
|
1053
1080
|
// src/tools/skills-run-script.ts
|
|
1054
|
-
var SINGLE_QUOTE_RE = /'/g;
|
|
1055
1081
|
var ABS_WINDOWS_RE = /^[a-z]:[\\/]/i;
|
|
1056
1082
|
var COLLAPSE_SLASHES_RE = /\/+/g;
|
|
1057
|
-
function quoteShellArg(arg) {
|
|
1058
|
-
return `'${arg.replace(SINGLE_QUOTE_RE, `'\\''`)}'`;
|
|
1059
|
-
}
|
|
1060
1083
|
function createSkillsRunScriptTool(options) {
|
|
1061
1084
|
const byName = new Map(options.catalog.map((s) => [s.name, s]));
|
|
1062
1085
|
const timeoutMs = options.scriptTimeoutMs ?? 6e4;
|
|
@@ -1103,7 +1126,7 @@ function createSkillsRunScriptTool(options) {
|
|
|
1103
1126
|
const validated = validateResourcePath(joinedPath, skill.baseDir);
|
|
1104
1127
|
if (!validated.valid)
|
|
1105
1128
|
return `Error: ${validated.error}`;
|
|
1106
|
-
const cmd = [validated.absolutePath, ...args].map(
|
|
1129
|
+
const cmd = [validated.absolutePath, ...args].map(alwaysQuote).join(" ");
|
|
1107
1130
|
try {
|
|
1108
1131
|
const result = await ctx.execution.exec(ctx.handle, cmd, {
|
|
1109
1132
|
timeout: Math.max(1, Math.round(timeoutMs / 1e3))
|
|
@@ -1279,6 +1302,59 @@ function rewriteMessagesToWire(messages, maps) {
|
|
|
1279
1302
|
return messages.map((msg) => ({ ...msg, content: rewriteContentToWire(msg.content, maps) }));
|
|
1280
1303
|
}
|
|
1281
1304
|
|
|
1305
|
+
// src/dedup-tools.ts
|
|
1306
|
+
function installDedupToolsGate(hooks, getDedupTools, getSession) {
|
|
1307
|
+
const pending = /* @__PURE__ */ new Map();
|
|
1308
|
+
function pendingKey(callId, name) {
|
|
1309
|
+
return `${callId}::${name}`;
|
|
1310
|
+
}
|
|
1311
|
+
function gateHandler(ctx) {
|
|
1312
|
+
if (ctx.block || ctx.result !== void 0)
|
|
1313
|
+
return;
|
|
1314
|
+
const dedupTools = getDedupTools();
|
|
1315
|
+
const hasher = dedupTools?.[ctx.name];
|
|
1316
|
+
if (!hasher)
|
|
1317
|
+
return;
|
|
1318
|
+
const session = getSession();
|
|
1319
|
+
const state = getToolDedupState(session);
|
|
1320
|
+
if (!state)
|
|
1321
|
+
return;
|
|
1322
|
+
let hash;
|
|
1323
|
+
try {
|
|
1324
|
+
hash = hasher(ctx.input);
|
|
1325
|
+
} catch {
|
|
1326
|
+
return;
|
|
1327
|
+
}
|
|
1328
|
+
if (typeof hash !== "string" || hash.length === 0)
|
|
1329
|
+
return;
|
|
1330
|
+
const prior = state.get(ctx.name);
|
|
1331
|
+
if (prior && prior.hash === hash) {
|
|
1332
|
+
ctx.result = prior.result;
|
|
1333
|
+
return;
|
|
1334
|
+
}
|
|
1335
|
+
pending.set(pendingKey(ctx.callId, ctx.name), hash);
|
|
1336
|
+
}
|
|
1337
|
+
function afterHandler(ctx) {
|
|
1338
|
+
const key = pendingKey(ctx.callId, ctx.name);
|
|
1339
|
+
const hash = pending.get(key);
|
|
1340
|
+
if (hash === void 0)
|
|
1341
|
+
return;
|
|
1342
|
+
pending.delete(key);
|
|
1343
|
+
const session = getSession();
|
|
1344
|
+
const state = getToolDedupState(session);
|
|
1345
|
+
if (!state)
|
|
1346
|
+
return;
|
|
1347
|
+
state.set(ctx.name, { hash, result: ctx.result });
|
|
1348
|
+
}
|
|
1349
|
+
const unregisterGate = hooks.hook("tool:gate", gateHandler);
|
|
1350
|
+
const unregisterAfter = hooks.hook("tool:after", afterHandler);
|
|
1351
|
+
return function uninstall() {
|
|
1352
|
+
unregisterGate();
|
|
1353
|
+
unregisterAfter();
|
|
1354
|
+
pending.clear();
|
|
1355
|
+
};
|
|
1356
|
+
}
|
|
1357
|
+
|
|
1282
1358
|
// src/tools/validation.ts
|
|
1283
1359
|
var TRUE_STRINGS = /* @__PURE__ */ new Set(["true", "True", "TRUE", "1", "yes", "Yes", "YES"]);
|
|
1284
1360
|
var FALSE_STRINGS = /* @__PURE__ */ new Set(["false", "False", "FALSE", "0", "no", "No", "NO"]);
|
|
@@ -1434,6 +1510,24 @@ function formatValue(value) {
|
|
|
1434
1510
|
|
|
1435
1511
|
// src/loop.ts
|
|
1436
1512
|
var IMAGE_OMITTED_MARKER = "[image omitted \u2014 model does not support vision]";
|
|
1513
|
+
function applyThinkingDecay(baseBudget, decay, turn) {
|
|
1514
|
+
if (typeof baseBudget !== "number" || baseBudget <= 0)
|
|
1515
|
+
return baseBudget;
|
|
1516
|
+
if (!decay)
|
|
1517
|
+
return baseBudget;
|
|
1518
|
+
let raw;
|
|
1519
|
+
if (typeof decay === "function") {
|
|
1520
|
+
raw = decay(turn, baseBudget);
|
|
1521
|
+
} else {
|
|
1522
|
+
if (turn <= decay.afterTurn)
|
|
1523
|
+
return baseBudget;
|
|
1524
|
+
const k = turn - decay.afterTurn;
|
|
1525
|
+
raw = Math.max(decay.floor, baseBudget * decay.factor ** k);
|
|
1526
|
+
}
|
|
1527
|
+
if (Number.isNaN(raw) || raw <= 0)
|
|
1528
|
+
return 0;
|
|
1529
|
+
return Math.round(Math.min(baseBudget, raw));
|
|
1530
|
+
}
|
|
1437
1531
|
function turnsToMessages(turns) {
|
|
1438
1532
|
return turns.filter((t) => t.role !== "system").map((t) => ({ role: t.role, content: t.content }));
|
|
1439
1533
|
}
|
|
@@ -1589,6 +1683,7 @@ async function executeTurn(ctx, turn) {
|
|
|
1589
1683
|
const keep = typeof ctx.compactKeepTurns === "number" && ctx.compactKeepTurns >= 0 ? ctx.compactKeepTurns : 4;
|
|
1590
1684
|
sanitizedMessages = applyTailCompaction(sanitizedMessages, threshold, keep);
|
|
1591
1685
|
}
|
|
1686
|
+
const effectiveThinkingBudget = applyThinkingDecay(ctx.thinkingBudget, ctx.thinkingDecay, turn);
|
|
1592
1687
|
const streamOptions = {
|
|
1593
1688
|
model: ctx.model,
|
|
1594
1689
|
system: ctx.system,
|
|
@@ -1596,13 +1691,22 @@ async function executeTurn(ctx, turn) {
|
|
|
1596
1691
|
messages: sanitizedMessages,
|
|
1597
1692
|
maxTokens: ctx.maxTokens ?? 16384,
|
|
1598
1693
|
thinking: ctx.thinking,
|
|
1599
|
-
thinkingBudget:
|
|
1694
|
+
thinkingBudget: effectiveThinkingBudget,
|
|
1600
1695
|
cache: ctx.cache ?? true,
|
|
1601
1696
|
signal: ctx.signal
|
|
1602
1697
|
};
|
|
1603
1698
|
const transformCtx = { messages: streamOptions.messages };
|
|
1604
1699
|
await ctx.hooks.callHook("context:transform", transformCtx);
|
|
1605
1700
|
streamOptions.messages = transformCtx.messages;
|
|
1701
|
+
const systemCtx = {
|
|
1702
|
+
system: streamOptions.system,
|
|
1703
|
+
messages: streamOptions.messages,
|
|
1704
|
+
turn,
|
|
1705
|
+
turnId,
|
|
1706
|
+
...ctx.session ? { session: ctx.session } : {}
|
|
1707
|
+
};
|
|
1708
|
+
await ctx.hooks.callHook("system:transform", systemCtx);
|
|
1709
|
+
streamOptions.system = systemCtx.system;
|
|
1606
1710
|
await ctx.hooks.callHook("turn:before", { turn, turnId, options: streamOptions });
|
|
1607
1711
|
let currentText = "";
|
|
1608
1712
|
let currentThinking = "";
|
|
@@ -1626,16 +1730,23 @@ async function executeTurn(ctx, turn) {
|
|
|
1626
1730
|
);
|
|
1627
1731
|
} catch (err) {
|
|
1628
1732
|
const errorUsage = { input: 0, output: 0 };
|
|
1733
|
+
const errorContent = currentText ? [{ type: "text", text: currentText }] : [{ type: "text", text: "[provider error before any output]" }];
|
|
1629
1734
|
const errorTurn = {
|
|
1630
1735
|
id: turnId,
|
|
1631
1736
|
runId: ctx.runId,
|
|
1632
1737
|
role: "assistant",
|
|
1633
|
-
content:
|
|
1738
|
+
content: errorContent,
|
|
1634
1739
|
usage: errorUsage,
|
|
1635
1740
|
createdAt: Date.now()
|
|
1636
1741
|
};
|
|
1637
1742
|
ctx.turns.push(errorTurn);
|
|
1638
|
-
await ctx.hooks.callHook("turn:after", {
|
|
1743
|
+
await ctx.hooks.callHook("turn:after", {
|
|
1744
|
+
turn,
|
|
1745
|
+
turnId,
|
|
1746
|
+
usage: errorUsage,
|
|
1747
|
+
message: errorTurn,
|
|
1748
|
+
toolCounts: { turn: Object.freeze({}), run: Object.freeze({ ...ctx.runToolCounts }) }
|
|
1749
|
+
});
|
|
1639
1750
|
throw wrapProviderError(err, ctx);
|
|
1640
1751
|
}
|
|
1641
1752
|
if (currentText) {
|
|
@@ -1658,7 +1769,16 @@ async function executeTurn(ctx, turn) {
|
|
|
1658
1769
|
createdAt: Date.now()
|
|
1659
1770
|
};
|
|
1660
1771
|
ctx.turns.push(assistantTurn);
|
|
1661
|
-
|
|
1772
|
+
const turnCounts = {};
|
|
1773
|
+
for (const tc of canonicalToolCalls)
|
|
1774
|
+
turnCounts[tc.name] = (turnCounts[tc.name] ?? 0) + 1;
|
|
1775
|
+
await ctx.hooks.callHook("turn:after", {
|
|
1776
|
+
turn,
|
|
1777
|
+
turnId,
|
|
1778
|
+
usage: result.usage,
|
|
1779
|
+
message: assistantTurn,
|
|
1780
|
+
toolCounts: { turn: Object.freeze(turnCounts), run: Object.freeze({ ...ctx.runToolCounts }) }
|
|
1781
|
+
});
|
|
1662
1782
|
if (result.done) {
|
|
1663
1783
|
if (ctx.schema && !ctx.signal.aborted) {
|
|
1664
1784
|
const outputSpec = {
|
|
@@ -1771,6 +1891,7 @@ async function executeSingleTool(ctx, call, turnId) {
|
|
|
1771
1891
|
const toolDef = ctx.tools[call.name];
|
|
1772
1892
|
const callId = call.id;
|
|
1773
1893
|
const displayName = toWireName(call.name, ctx.aliasMaps);
|
|
1894
|
+
const runToolCounts = Object.freeze({ ...ctx.runToolCounts });
|
|
1774
1895
|
const gateCtx = {
|
|
1775
1896
|
turnId,
|
|
1776
1897
|
callId,
|
|
@@ -1778,12 +1899,27 @@ async function executeSingleTool(ctx, call, turnId) {
|
|
|
1778
1899
|
displayName,
|
|
1779
1900
|
input: call.input,
|
|
1780
1901
|
block: false,
|
|
1781
|
-
reason: "Tool execution was blocked"
|
|
1902
|
+
reason: "Tool execution was blocked",
|
|
1903
|
+
runToolCounts
|
|
1782
1904
|
};
|
|
1783
1905
|
await ctx.hooks.callHook("tool:gate", gateCtx);
|
|
1784
1906
|
if (gateCtx.block) {
|
|
1785
1907
|
return { result: { id: callId, content: `Blocked: ${gateCtx.reason}` } };
|
|
1786
1908
|
}
|
|
1909
|
+
ctx.runToolCounts[call.name] = (ctx.runToolCounts[call.name] ?? 0) + 1;
|
|
1910
|
+
if (gateCtx.result !== void 0) {
|
|
1911
|
+
const substitute = await emitToolResult(ctx, {
|
|
1912
|
+
turnId,
|
|
1913
|
+
callId,
|
|
1914
|
+
name: call.name,
|
|
1915
|
+
displayName,
|
|
1916
|
+
input: gateCtx.input,
|
|
1917
|
+
output: gateCtx.result,
|
|
1918
|
+
isError: false,
|
|
1919
|
+
runToolCounts
|
|
1920
|
+
});
|
|
1921
|
+
return { result: { id: callId, content: substitute } };
|
|
1922
|
+
}
|
|
1787
1923
|
let effectiveInput = gateCtx.input;
|
|
1788
1924
|
if (!toolDef) {
|
|
1789
1925
|
const unknownCtx = {
|
|
@@ -1841,6 +1977,7 @@ async function executeSingleTool(ctx, call, turnId) {
|
|
|
1841
1977
|
name: call.name,
|
|
1842
1978
|
displayName,
|
|
1843
1979
|
input: effectiveInput,
|
|
1980
|
+
runToolCounts,
|
|
1844
1981
|
...coercions ? { coercions } : {}
|
|
1845
1982
|
});
|
|
1846
1983
|
let output;
|
|
@@ -1880,12 +2017,29 @@ async function executeSingleTool(ctx, call, turnId) {
|
|
|
1880
2017
|
output = errorCtx.result ?? `Tool error: ${error.message}`;
|
|
1881
2018
|
isError = true;
|
|
1882
2019
|
}
|
|
1883
|
-
const
|
|
2020
|
+
const finalOutput = await emitToolResult(ctx, {
|
|
1884
2021
|
turnId,
|
|
1885
2022
|
callId,
|
|
1886
2023
|
name: call.name,
|
|
1887
2024
|
displayName,
|
|
1888
2025
|
input: effectiveInput,
|
|
2026
|
+
output,
|
|
2027
|
+
isError,
|
|
2028
|
+
runToolCounts,
|
|
2029
|
+
...coercions ? { coercions } : {}
|
|
2030
|
+
});
|
|
2031
|
+
return { result: { id: callId, content: finalOutput } };
|
|
2032
|
+
}
|
|
2033
|
+
async function emitToolResult(ctx, params) {
|
|
2034
|
+
const { turnId, callId, name, displayName, input, runToolCounts, coercions } = params;
|
|
2035
|
+
let output = params.output;
|
|
2036
|
+
let isError = params.isError;
|
|
2037
|
+
const transformCtx = {
|
|
2038
|
+
turnId,
|
|
2039
|
+
callId,
|
|
2040
|
+
name,
|
|
2041
|
+
displayName,
|
|
2042
|
+
input,
|
|
1889
2043
|
result: output,
|
|
1890
2044
|
isError,
|
|
1891
2045
|
outputBytes: toolOutputByteLength(output),
|
|
@@ -1898,14 +2052,15 @@ async function executeSingleTool(ctx, call, turnId) {
|
|
|
1898
2052
|
await ctx.hooks.callHook("tool:after", {
|
|
1899
2053
|
turnId,
|
|
1900
2054
|
callId,
|
|
1901
|
-
name
|
|
2055
|
+
name,
|
|
1902
2056
|
displayName,
|
|
1903
|
-
input
|
|
2057
|
+
input,
|
|
1904
2058
|
result: output,
|
|
1905
2059
|
outputBytes: toolOutputByteLength(output),
|
|
2060
|
+
runToolCounts,
|
|
1906
2061
|
...coercions ? { coercions } : {}
|
|
1907
2062
|
});
|
|
1908
|
-
return
|
|
2063
|
+
return output;
|
|
1909
2064
|
}
|
|
1910
2065
|
async function executeToolsSequential(ctx, toolCalls, turnId) {
|
|
1911
2066
|
const results = [];
|
|
@@ -1913,28 +2068,10 @@ async function executeToolsSequential(ctx, toolCalls, turnId) {
|
|
|
1913
2068
|
if (ctx.signal.aborted)
|
|
1914
2069
|
break;
|
|
1915
2070
|
if (ctx.steeringQueue.length > 0) {
|
|
1916
|
-
const
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
}
|
|
1921
|
-
const toolResultMsg = ctx.provider.toolResultsMessage(results);
|
|
1922
|
-
ctx.turns.push({
|
|
1923
|
-
id: await ctx.generateTurnId(),
|
|
1924
|
-
runId: ctx.runId,
|
|
1925
|
-
role: toolResultMsg.role,
|
|
1926
|
-
content: toolResultMsg.content,
|
|
1927
|
-
createdAt: Date.now()
|
|
1928
|
-
});
|
|
1929
|
-
const steerUserMsg = ctx.provider.userMessage(steerMsg);
|
|
1930
|
-
ctx.turns.push({
|
|
1931
|
-
id: await ctx.generateTurnId(),
|
|
1932
|
-
runId: ctx.runId,
|
|
1933
|
-
role: steerUserMsg.role,
|
|
1934
|
-
content: steerUserMsg.content,
|
|
1935
|
-
createdAt: Date.now()
|
|
1936
|
-
});
|
|
1937
|
-
return [];
|
|
2071
|
+
const fromIdx = toolCalls.indexOf(call);
|
|
2072
|
+
for (let i = fromIdx; i < toolCalls.length; i++)
|
|
2073
|
+
results.push({ id: toolCalls[i].id, content: "Skipped: steering message received" });
|
|
2074
|
+
return results;
|
|
1938
2075
|
}
|
|
1939
2076
|
const { result } = await executeSingleTool(ctx, call, turnId);
|
|
1940
2077
|
results.push(result);
|
|
@@ -1978,6 +2115,15 @@ function canonicalizePrompt(prompt, legacyImages) {
|
|
|
1978
2115
|
}
|
|
1979
2116
|
if (prompt.length === 0)
|
|
1980
2117
|
return void 0;
|
|
2118
|
+
for (const part of prompt) {
|
|
2119
|
+
if (!part || typeof part !== "object" || typeof part.type !== "string") {
|
|
2120
|
+
throw new Error("Invalid PromptPart: each part must be an object with a `type` field.");
|
|
2121
|
+
}
|
|
2122
|
+
const type = part.type;
|
|
2123
|
+
if (type !== "text" && type !== "image" && type !== "document") {
|
|
2124
|
+
throw new Error(`Invalid PromptPart type "${type}". Expected "text" | "image" | "document".`);
|
|
2125
|
+
}
|
|
2126
|
+
}
|
|
1981
2127
|
const hasMeaningfulPart = prompt.some((part) => part.type === "text" && part.text.length > 0 || part.type === "image" || part.type === "document");
|
|
1982
2128
|
if (!hasMeaningfulPart)
|
|
1983
2129
|
return void 0;
|
|
@@ -2014,6 +2160,81 @@ function buildPromptMessage(provider, parts) {
|
|
|
2014
2160
|
return defaultPromptMessage(parts);
|
|
2015
2161
|
}
|
|
2016
2162
|
|
|
2163
|
+
// src/tool-budgets.ts
|
|
2164
|
+
function installToolBudgetsGate(hooks, getToolBudgets, enqueueSteer) {
|
|
2165
|
+
const steeredOnce = /* @__PURE__ */ new Set();
|
|
2166
|
+
const approvedCounts = {};
|
|
2167
|
+
async function gateHandler(ctx) {
|
|
2168
|
+
if (ctx.block || ctx.result !== void 0)
|
|
2169
|
+
return;
|
|
2170
|
+
const toolBudgets = getToolBudgets();
|
|
2171
|
+
const budget = toolBudgets?.[ctx.name];
|
|
2172
|
+
if (!budget)
|
|
2173
|
+
return;
|
|
2174
|
+
const max = budget.max;
|
|
2175
|
+
if (typeof max !== "number" || max <= 0)
|
|
2176
|
+
return;
|
|
2177
|
+
const count = approvedCounts[ctx.name] ?? 0;
|
|
2178
|
+
if (count < max) {
|
|
2179
|
+
approvedCounts[ctx.name] = count + 1;
|
|
2180
|
+
return;
|
|
2181
|
+
}
|
|
2182
|
+
const onExceed = budget.onExceed ?? "steer";
|
|
2183
|
+
let mode;
|
|
2184
|
+
let message;
|
|
2185
|
+
if (typeof onExceed === "function") {
|
|
2186
|
+
try {
|
|
2187
|
+
const out = onExceed({ tool: ctx.name, count, max });
|
|
2188
|
+
mode = out.mode;
|
|
2189
|
+
message = out.message;
|
|
2190
|
+
} catch {
|
|
2191
|
+
mode = "steer";
|
|
2192
|
+
message = defaultSteerMessage(ctx.name, count, max);
|
|
2193
|
+
}
|
|
2194
|
+
} else if (onExceed === "block") {
|
|
2195
|
+
mode = "block";
|
|
2196
|
+
message = defaultBlockMessage(ctx.name, max);
|
|
2197
|
+
} else {
|
|
2198
|
+
mode = "steer";
|
|
2199
|
+
message = defaultSteerMessage(ctx.name, count, max);
|
|
2200
|
+
}
|
|
2201
|
+
if (mode === "block") {
|
|
2202
|
+
ctx.block = true;
|
|
2203
|
+
ctx.reason = message;
|
|
2204
|
+
await hooks.callHook("tool-budget:exceeded", {
|
|
2205
|
+
tool: ctx.name,
|
|
2206
|
+
count,
|
|
2207
|
+
max,
|
|
2208
|
+
turnId: ctx.turnId,
|
|
2209
|
+
mode: "block"
|
|
2210
|
+
});
|
|
2211
|
+
return;
|
|
2212
|
+
}
|
|
2213
|
+
if (!steeredOnce.has(ctx.name)) {
|
|
2214
|
+
steeredOnce.add(ctx.name);
|
|
2215
|
+
enqueueSteer(message);
|
|
2216
|
+
await hooks.callHook("tool-budget:exceeded", {
|
|
2217
|
+
tool: ctx.name,
|
|
2218
|
+
count,
|
|
2219
|
+
max,
|
|
2220
|
+
turnId: ctx.turnId,
|
|
2221
|
+
mode: "steer"
|
|
2222
|
+
});
|
|
2223
|
+
}
|
|
2224
|
+
}
|
|
2225
|
+
const unregister = hooks.hook("tool:gate", gateHandler);
|
|
2226
|
+
return function uninstall() {
|
|
2227
|
+
unregister();
|
|
2228
|
+
steeredOnce.clear();
|
|
2229
|
+
};
|
|
2230
|
+
}
|
|
2231
|
+
function defaultSteerMessage(tool, count, max) {
|
|
2232
|
+
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.]`;
|
|
2233
|
+
}
|
|
2234
|
+
function defaultBlockMessage(tool, max) {
|
|
2235
|
+
return `Tool '${tool}' has reached its per-run budget of ${max} calls; further invocations are refused.`;
|
|
2236
|
+
}
|
|
2237
|
+
|
|
2017
2238
|
// src/agent.ts
|
|
2018
2239
|
var HOOK_EVENT_NAMES = [
|
|
2019
2240
|
"system:before",
|
|
@@ -2032,6 +2253,7 @@ var HOOK_EVENT_NAMES = [
|
|
|
2032
2253
|
"validation:reject",
|
|
2033
2254
|
"validation:coerce",
|
|
2034
2255
|
"context:transform",
|
|
2256
|
+
"system:transform",
|
|
2035
2257
|
"steer:inject",
|
|
2036
2258
|
"spawn:before",
|
|
2037
2259
|
"spawn:complete",
|
|
@@ -2060,6 +2282,7 @@ var HOOK_EVENT_NAMES = [
|
|
|
2060
2282
|
"usage",
|
|
2061
2283
|
"output",
|
|
2062
2284
|
"budget:exceeded",
|
|
2285
|
+
"tool-budget:exceeded",
|
|
2063
2286
|
"agent:abort",
|
|
2064
2287
|
"agent:done",
|
|
2065
2288
|
"session:start",
|
|
@@ -2083,7 +2306,12 @@ function resolveBehavior(agentBehavior, runBehavior) {
|
|
|
2083
2306
|
toolOutputBudget: runBehavior?.toolOutputBudget ?? agentBehavior?.toolOutputBudget,
|
|
2084
2307
|
compactStrategy: runBehavior?.compactStrategy ?? agentBehavior?.compactStrategy ?? "off",
|
|
2085
2308
|
compactThreshold: runBehavior?.compactThreshold ?? agentBehavior?.compactThreshold,
|
|
2086
|
-
compactKeepTurns: runBehavior?.compactKeepTurns ?? agentBehavior?.compactKeepTurns
|
|
2309
|
+
compactKeepTurns: runBehavior?.compactKeepTurns ?? agentBehavior?.compactKeepTurns,
|
|
2310
|
+
thinkingDecay: runBehavior?.thinkingDecay ?? agentBehavior?.thinkingDecay,
|
|
2311
|
+
dedupReads: runBehavior?.dedupReads ?? agentBehavior?.dedupReads,
|
|
2312
|
+
dedupTools: runBehavior?.dedupTools ?? agentBehavior?.dedupTools,
|
|
2313
|
+
requireReadBeforeEdit: runBehavior?.requireReadBeforeEdit ?? agentBehavior?.requireReadBeforeEdit,
|
|
2314
|
+
toolBudgets: runBehavior?.toolBudgets ?? agentBehavior?.toolBudgets
|
|
2087
2315
|
};
|
|
2088
2316
|
}
|
|
2089
2317
|
function createAgent({ provider, name: agentName, system: agentSystem, tools: agentTools, toolAliases, behavior: agentBehavior, execution, mcpServers, session, skills: agentSkills, mcpConnector, eager }) {
|
|
@@ -2107,6 +2335,8 @@ function createAgent({ provider, name: agentName, system: agentSystem, tools: ag
|
|
|
2107
2335
|
const skillsDisabled = skillsEnabledValue === false || Array.isArray(skillsEnabledValue) && skillsEnabledValue.length === 0;
|
|
2108
2336
|
let resolvedSkills = null;
|
|
2109
2337
|
let skillsCatalog = null;
|
|
2338
|
+
let skillsCleanup = () => {
|
|
2339
|
+
};
|
|
2110
2340
|
const skillActivationState = createSkillActivationState({
|
|
2111
2341
|
maxActive: skillsConfig?.maxActive
|
|
2112
2342
|
});
|
|
@@ -2174,7 +2404,9 @@ function createAgent({ provider, name: agentName, system: agentSystem, tools: ag
|
|
|
2174
2404
|
await warmup();
|
|
2175
2405
|
}
|
|
2176
2406
|
if (!skillsDisabled && skillsConfig && !resolvedSkills) {
|
|
2177
|
-
|
|
2407
|
+
const bundle = await resolveSkillsWithCleanup(skillsConfig);
|
|
2408
|
+
resolvedSkills = bundle.skills;
|
|
2409
|
+
skillsCleanup = bundle.cleanup;
|
|
2178
2410
|
await hooks.callHook("skills:resolve", { skills: resolvedSkills });
|
|
2179
2411
|
const skillsToolRegistered = skillsConfig?.tool !== false && resolvedSkills.length > 0;
|
|
2180
2412
|
const catalogCtx = {
|
|
@@ -2206,7 +2438,8 @@ function createAgent({ provider, name: agentName, system: agentSystem, tools: ag
|
|
|
2206
2438
|
}
|
|
2207
2439
|
const thinking = options.thinking ?? "off";
|
|
2208
2440
|
const model = options.model ?? provider.meta.defaultModel;
|
|
2209
|
-
const
|
|
2441
|
+
const resolvedBehavior = resolveBehavior(agentBehavior, options.behavior);
|
|
2442
|
+
const { toolExecution, maxTurns, maxTokens, thinkingBudget, schema, cache, toolOutputBudget, compactStrategy, compactThreshold, compactKeepTurns, thinkingDecay, dedupTools, toolBudgets } = resolvedBehavior;
|
|
2210
2443
|
let system = options.system || agentSystem || "You are a helpful assistant.";
|
|
2211
2444
|
if (skillsCatalog) {
|
|
2212
2445
|
system = `${system}
|
|
@@ -2306,6 +2539,16 @@ ${skillsCatalog}`;
|
|
|
2306
2539
|
await hooks.callHook("session:end", { sessionId: session.id, runId, status, turnRange: [runTurnStart, turns.length - 1] });
|
|
2307
2540
|
}
|
|
2308
2541
|
const uninstallAllowedToolsGate = installAllowedToolsGate(hooks, skillActivationState);
|
|
2542
|
+
const uninstallToolBudgets = installToolBudgetsGate(
|
|
2543
|
+
hooks,
|
|
2544
|
+
() => toolBudgets,
|
|
2545
|
+
(msg) => steeringQueue.push(msg)
|
|
2546
|
+
);
|
|
2547
|
+
const uninstallDedupTools = installDedupToolsGate(
|
|
2548
|
+
hooks,
|
|
2549
|
+
() => dedupTools,
|
|
2550
|
+
() => session ?? void 0
|
|
2551
|
+
);
|
|
2309
2552
|
const runStartMs = Date.now();
|
|
2310
2553
|
const runDepth = typeof options.depth === "number" ? options.depth : 0;
|
|
2311
2554
|
try {
|
|
@@ -2318,7 +2561,10 @@ ${skillsCatalog}`;
|
|
|
2318
2561
|
agentToolAliases: toolAliases,
|
|
2319
2562
|
agentMcpServers: mcpServers,
|
|
2320
2563
|
agentSkills,
|
|
2321
|
-
|
|
2564
|
+
// Forward the resolved view (agent + run merged) so per-run overrides
|
|
2565
|
+
// of `dedupReads` / `requireReadBeforeEdit` / etc. are visible to
|
|
2566
|
+
// tools via `ToolContext.behavior`.
|
|
2567
|
+
agentBehavior: resolvedBehavior,
|
|
2322
2568
|
tools,
|
|
2323
2569
|
formattedTools,
|
|
2324
2570
|
aliasMaps,
|
|
@@ -2345,7 +2591,9 @@ ${skillsCatalog}`;
|
|
|
2345
2591
|
compactStrategy,
|
|
2346
2592
|
compactThreshold,
|
|
2347
2593
|
compactKeepTurns,
|
|
2348
|
-
|
|
2594
|
+
...thinkingDecay !== void 0 ? { thinkingDecay } : {},
|
|
2595
|
+
runStartMs,
|
|
2596
|
+
runToolCounts: {}
|
|
2349
2597
|
});
|
|
2350
2598
|
const finalStats = {
|
|
2351
2599
|
...stats,
|
|
@@ -2387,6 +2635,8 @@ ${skillsCatalog}`;
|
|
|
2387
2635
|
} finally {
|
|
2388
2636
|
await deactivateAllSkills();
|
|
2389
2637
|
uninstallAllowedToolsGate();
|
|
2638
|
+
uninstallDedupTools();
|
|
2639
|
+
uninstallToolBudgets();
|
|
2390
2640
|
unregisterSpawnHook();
|
|
2391
2641
|
unregisterSessionSync?.();
|
|
2392
2642
|
for (const unregister of perRunUnregisters)
|
|
@@ -2413,6 +2663,11 @@ ${skillsCatalog}`;
|
|
|
2413
2663
|
return idlePromise ?? Promise.resolve();
|
|
2414
2664
|
}
|
|
2415
2665
|
async function reset() {
|
|
2666
|
+
if (running) {
|
|
2667
|
+
throw new Error(
|
|
2668
|
+
"Cannot reset() while the agent is running. Call `agent.abort()` and `await agent.waitForIdle()` first."
|
|
2669
|
+
);
|
|
2670
|
+
}
|
|
2416
2671
|
conversationTurns = [];
|
|
2417
2672
|
steeringQueue.length = 0;
|
|
2418
2673
|
followUpQueue.length = 0;
|
|
@@ -2501,6 +2756,9 @@ ${skillsCatalog}`;
|
|
|
2501
2756
|
await executionContext.destroy(executionHandle);
|
|
2502
2757
|
executionHandle = null;
|
|
2503
2758
|
}
|
|
2759
|
+
skillsCleanup();
|
|
2760
|
+
skillsCleanup = () => {
|
|
2761
|
+
};
|
|
2504
2762
|
}
|
|
2505
2763
|
if (eager && allMcpServers.length > 0) {
|
|
2506
2764
|
void warmup().catch(() => {
|