@prestyj/agent 5.28.1 → 5.29.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.
- package/dist/index.cjs +488 -77
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +145 -5
- package/dist/index.d.ts +145 -5
- package/dist/index.js +460 -55
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.cjs
CHANGED
|
@@ -22,7 +22,12 @@ var index_exports = {};
|
|
|
22
22
|
__export(index_exports, {
|
|
23
23
|
Agent: () => Agent,
|
|
24
24
|
AgentStream: () => AgentStream,
|
|
25
|
+
DEFAULT_STREAM_RULE_MAX_RETRIES: () => DEFAULT_STREAM_RULE_MAX_RETRIES,
|
|
26
|
+
DEFAULT_STREAM_RULE_WINDOW_CHARS: () => DEFAULT_STREAM_RULE_WINDOW_CHARS,
|
|
27
|
+
JsonEscapeDecoder: () => JsonEscapeDecoder,
|
|
28
|
+
StreamRuleMonitor: () => StreamRuleMonitor,
|
|
25
29
|
agentLoop: () => agentLoop,
|
|
30
|
+
buildStreamRuleReminder: () => buildStreamRuleReminder,
|
|
26
31
|
cancelledBeforeStartText: () => cancelledBeforeStartText,
|
|
27
32
|
indeterminateOutcomeText: () => indeterminateOutcomeText,
|
|
28
33
|
isAbortError: () => isAbortError,
|
|
@@ -30,16 +35,17 @@ __export(index_exports, {
|
|
|
30
35
|
isContextOverflow: () => isContextOverflow,
|
|
31
36
|
isLocalBackendUrl: () => isLocalBackendUrl,
|
|
32
37
|
isUsageLimitError: () => isUsageLimitError,
|
|
38
|
+
repairToolPairingAdjacent: () => repairToolPairingAdjacent,
|
|
33
39
|
setStreamDiagnostic: () => setStreamDiagnostic
|
|
34
40
|
});
|
|
35
41
|
module.exports = __toCommonJS(index_exports);
|
|
36
42
|
|
|
37
43
|
// src/agent.ts
|
|
38
|
-
var
|
|
44
|
+
var import_ai3 = require("@prestyj/ai");
|
|
39
45
|
|
|
40
46
|
// src/agent-loop.ts
|
|
41
47
|
var import_zod = require("zod");
|
|
42
|
-
var
|
|
48
|
+
var import_ai2 = require("@prestyj/ai");
|
|
43
49
|
|
|
44
50
|
// src/local-backend.ts
|
|
45
51
|
function isLocalBackendUrl(baseUrl) {
|
|
@@ -63,6 +69,107 @@ function isLocalBackendUrl(baseUrl) {
|
|
|
63
69
|
return false;
|
|
64
70
|
}
|
|
65
71
|
|
|
72
|
+
// src/tool-call-hints.ts
|
|
73
|
+
var import_ai = require("@prestyj/ai");
|
|
74
|
+
function normalizeName(name) {
|
|
75
|
+
return name.toLowerCase().replace(/[^a-z0-9]/g, "");
|
|
76
|
+
}
|
|
77
|
+
function editDistance(a, b) {
|
|
78
|
+
let beforePrevious = [];
|
|
79
|
+
let previous = Array.from({ length: b.length + 1 }, (_, i) => i);
|
|
80
|
+
for (let i = 1; i <= a.length; i++) {
|
|
81
|
+
const current = [i];
|
|
82
|
+
for (let j = 1; j <= b.length; j++) {
|
|
83
|
+
const cost = a[i - 1] === b[j - 1] ? 0 : 1;
|
|
84
|
+
let distance = Math.min(
|
|
85
|
+
(previous[j] ?? 0) + 1,
|
|
86
|
+
(current[j - 1] ?? 0) + 1,
|
|
87
|
+
(previous[j - 1] ?? 0) + cost
|
|
88
|
+
);
|
|
89
|
+
if (i > 1 && j > 1 && a[i - 1] === b[j - 2] && a[i - 2] === b[j - 1]) {
|
|
90
|
+
distance = Math.min(distance, (beforePrevious[j - 2] ?? 0) + 1);
|
|
91
|
+
}
|
|
92
|
+
current[j] = distance;
|
|
93
|
+
}
|
|
94
|
+
beforePrevious = previous;
|
|
95
|
+
previous = current;
|
|
96
|
+
}
|
|
97
|
+
return previous[b.length] ?? 0;
|
|
98
|
+
}
|
|
99
|
+
function closestName(name, candidates) {
|
|
100
|
+
const target = normalizeName(name);
|
|
101
|
+
if (!target) return void 0;
|
|
102
|
+
const limit = Math.min(3, Math.max(1, Math.floor(target.length / 3)));
|
|
103
|
+
let best;
|
|
104
|
+
for (const candidate of [...candidates].sort()) {
|
|
105
|
+
if (candidate === name) continue;
|
|
106
|
+
const distance = editDistance(target, normalizeName(candidate));
|
|
107
|
+
if (distance > limit) continue;
|
|
108
|
+
if (!best || distance < best.distance) best = { name: candidate, distance };
|
|
109
|
+
}
|
|
110
|
+
return best?.name;
|
|
111
|
+
}
|
|
112
|
+
function unknownToolMessage(name, available) {
|
|
113
|
+
const suggestion = closestName(name, available);
|
|
114
|
+
if (suggestion) return `Unknown tool: ${name}. Did you mean \`${suggestion}\`?`;
|
|
115
|
+
const names = [...available].sort();
|
|
116
|
+
const listed = names.length > 40 ? `${names.slice(0, 40).join(", ")}, \u2026` : names.join(", ");
|
|
117
|
+
const searchHint = available.includes("tool_search") ? " If the capability is not listed, call `tool_search` to load it." : "";
|
|
118
|
+
return `Unknown tool: ${name}. Available tools: ${listed}.${searchHint}`;
|
|
119
|
+
}
|
|
120
|
+
function isRecord(value) {
|
|
121
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
122
|
+
}
|
|
123
|
+
function typeLabel(node) {
|
|
124
|
+
if (!isRecord(node)) return "any";
|
|
125
|
+
if (Array.isArray(node.enum)) {
|
|
126
|
+
return node.enum.map((value) => JSON.stringify(value)).join(" | ");
|
|
127
|
+
}
|
|
128
|
+
const variants = node.anyOf ?? node.oneOf;
|
|
129
|
+
if (Array.isArray(variants)) {
|
|
130
|
+
return [...new Set(variants.map(typeLabel))].filter((t) => t !== "null").join(" | ") || "any";
|
|
131
|
+
}
|
|
132
|
+
if (node.type === "array") return `${typeLabel(node.items)}[]`;
|
|
133
|
+
if (Array.isArray(node.type)) {
|
|
134
|
+
return node.type.filter((t) => t !== "null").join(" | ") || "any";
|
|
135
|
+
}
|
|
136
|
+
return typeof node.type === "string" ? node.type : "any";
|
|
137
|
+
}
|
|
138
|
+
function objectShape(tool) {
|
|
139
|
+
let schema;
|
|
140
|
+
try {
|
|
141
|
+
schema = (0, import_ai.resolveToolSchema)(tool);
|
|
142
|
+
} catch {
|
|
143
|
+
return void 0;
|
|
144
|
+
}
|
|
145
|
+
if (!isRecord(schema) || !isRecord(schema.properties)) return void 0;
|
|
146
|
+
const required = new Set(
|
|
147
|
+
Array.isArray(schema.required) ? schema.required.filter((r) => typeof r === "string") : []
|
|
148
|
+
);
|
|
149
|
+
return { properties: schema.properties, required };
|
|
150
|
+
}
|
|
151
|
+
function argumentHints(tool, args) {
|
|
152
|
+
const shape = objectShape(tool);
|
|
153
|
+
if (!shape) return "";
|
|
154
|
+
const names = Object.keys(shape.properties);
|
|
155
|
+
if (names.length === 0) return `Expected arguments: none.`;
|
|
156
|
+
const fields = names.map((name) => {
|
|
157
|
+
const optional = shape.required.has(name) ? "" : "?";
|
|
158
|
+
return `${name}${optional}: ${typeLabel(shape.properties[name])}`;
|
|
159
|
+
});
|
|
160
|
+
const lines = [`Expected arguments: { ${fields.join(", ")} }`];
|
|
161
|
+
if (isRecord(args)) {
|
|
162
|
+
for (const key of Object.keys(args).sort()) {
|
|
163
|
+
if (key in shape.properties) continue;
|
|
164
|
+
const suggestion = closestName(key, names);
|
|
165
|
+
lines.push(
|
|
166
|
+
suggestion ? `Unknown field \`${key}\` \u2014 did you mean \`${suggestion}\`?` : `Unknown field \`${key}\` is not a parameter of this tool.`
|
|
167
|
+
);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
return lines.join("\n");
|
|
171
|
+
}
|
|
172
|
+
|
|
66
173
|
// src/output-ceiling.ts
|
|
67
174
|
var TTL_MS = 24 * 60 * 60 * 1e3;
|
|
68
175
|
var MIN_PLAUSIBLE_CEILING = 256;
|
|
@@ -117,12 +224,162 @@ function clampOutputTokens(key, requested) {
|
|
|
117
224
|
return requested === void 0 ? ceiling : Math.min(requested, ceiling);
|
|
118
225
|
}
|
|
119
226
|
|
|
227
|
+
// src/stream-rules.ts
|
|
228
|
+
var DEFAULT_STREAM_RULE_MAX_RETRIES = 3;
|
|
229
|
+
var DEFAULT_STREAM_RULE_WINDOW_CHARS = 1024;
|
|
230
|
+
var JSON_ESCAPES = {
|
|
231
|
+
n: "\n",
|
|
232
|
+
r: "\r",
|
|
233
|
+
t: " ",
|
|
234
|
+
b: "\b",
|
|
235
|
+
f: "\f",
|
|
236
|
+
'"': '"',
|
|
237
|
+
"\\": "\\",
|
|
238
|
+
"/": "/"
|
|
239
|
+
};
|
|
240
|
+
var JsonEscapeDecoder = class {
|
|
241
|
+
pending = "";
|
|
242
|
+
push(chunk) {
|
|
243
|
+
const input = this.pending + chunk;
|
|
244
|
+
this.pending = "";
|
|
245
|
+
if (!input.includes("\\")) return input;
|
|
246
|
+
let out = "";
|
|
247
|
+
let i = 0;
|
|
248
|
+
while (i < input.length) {
|
|
249
|
+
const ch = input.charAt(i);
|
|
250
|
+
if (ch !== "\\") {
|
|
251
|
+
out += ch;
|
|
252
|
+
i++;
|
|
253
|
+
continue;
|
|
254
|
+
}
|
|
255
|
+
if (i + 1 >= input.length) {
|
|
256
|
+
this.pending = input.slice(i);
|
|
257
|
+
break;
|
|
258
|
+
}
|
|
259
|
+
const next = input.charAt(i + 1);
|
|
260
|
+
if (next === "u") {
|
|
261
|
+
if (i + 6 > input.length) {
|
|
262
|
+
this.pending = input.slice(i);
|
|
263
|
+
break;
|
|
264
|
+
}
|
|
265
|
+
const hex = input.slice(i + 2, i + 6);
|
|
266
|
+
out += /^[0-9a-fA-F]{4}$/.test(hex) ? String.fromCharCode(Number.parseInt(hex, 16)) : input.slice(i, i + 6);
|
|
267
|
+
i += 6;
|
|
268
|
+
continue;
|
|
269
|
+
}
|
|
270
|
+
out += JSON_ESCAPES[next] ?? next;
|
|
271
|
+
i += 2;
|
|
272
|
+
}
|
|
273
|
+
return out;
|
|
274
|
+
}
|
|
275
|
+
};
|
|
276
|
+
var StreamRuleMonitor = class {
|
|
277
|
+
maxRetries;
|
|
278
|
+
rules;
|
|
279
|
+
windowChars;
|
|
280
|
+
fired = /* @__PURE__ */ new Set();
|
|
281
|
+
retries = 0;
|
|
282
|
+
textWindow = "";
|
|
283
|
+
toolStreams = /* @__PURE__ */ new Map();
|
|
284
|
+
constructor(config) {
|
|
285
|
+
this.rules = config.rules;
|
|
286
|
+
this.maxRetries = Math.max(0, config.maxRetries ?? DEFAULT_STREAM_RULE_MAX_RETRIES);
|
|
287
|
+
this.windowChars = Math.max(64, config.windowChars ?? DEFAULT_STREAM_RULE_WINDOW_CHARS);
|
|
288
|
+
}
|
|
289
|
+
/** False once the retry cap is spent or every rule has fired — skip all matching. */
|
|
290
|
+
get active() {
|
|
291
|
+
return this.retries < this.maxRetries && this.fired.size < this.rules.length;
|
|
292
|
+
}
|
|
293
|
+
get retriesUsed() {
|
|
294
|
+
return this.retries;
|
|
295
|
+
}
|
|
296
|
+
beginAttempt() {
|
|
297
|
+
this.textWindow = "";
|
|
298
|
+
this.toolStreams.clear();
|
|
299
|
+
}
|
|
300
|
+
checkText(delta) {
|
|
301
|
+
if (!this.active || !delta) return null;
|
|
302
|
+
const scan = this.textWindow + delta;
|
|
303
|
+
this.textWindow = scan.slice(-this.windowChars);
|
|
304
|
+
const rules = this.match(scan, "text");
|
|
305
|
+
return rules.length > 0 ? { rules, source: "text" } : null;
|
|
306
|
+
}
|
|
307
|
+
checkToolArgs(id, toolName, argsDelta) {
|
|
308
|
+
if (!this.active || !argsDelta) return null;
|
|
309
|
+
let state = this.toolStreams.get(id);
|
|
310
|
+
if (!state) {
|
|
311
|
+
state = { window: "", decoder: new JsonEscapeDecoder() };
|
|
312
|
+
this.toolStreams.set(id, state);
|
|
313
|
+
}
|
|
314
|
+
const scan = state.window + state.decoder.push(argsDelta);
|
|
315
|
+
state.window = scan.slice(-this.windowChars);
|
|
316
|
+
const rules = this.match(scan, "tool", toolName);
|
|
317
|
+
return rules.length > 0 ? { rules, source: "tool", toolName } : null;
|
|
318
|
+
}
|
|
319
|
+
/** Consume one retry and mark the rules fired. Returns the retry ordinal (1-based). */
|
|
320
|
+
recordTrigger(rules) {
|
|
321
|
+
for (const rule of rules) this.fired.add(rule.name);
|
|
322
|
+
this.retries++;
|
|
323
|
+
return this.retries;
|
|
324
|
+
}
|
|
325
|
+
match(scan, source, toolName) {
|
|
326
|
+
const hits = [];
|
|
327
|
+
for (const rule of this.rules) {
|
|
328
|
+
if (this.fired.has(rule.name)) continue;
|
|
329
|
+
if (rule.scope !== "both" && rule.scope !== source) continue;
|
|
330
|
+
if (source === "tool" && rule.tools && toolName !== void 0) {
|
|
331
|
+
if (!rule.tools.includes(toolName)) continue;
|
|
332
|
+
}
|
|
333
|
+
rule.pattern.lastIndex = 0;
|
|
334
|
+
if (rule.pattern.test(scan)) hits.push(rule);
|
|
335
|
+
}
|
|
336
|
+
return hits;
|
|
337
|
+
}
|
|
338
|
+
};
|
|
339
|
+
function buildStreamRuleReminder(rules) {
|
|
340
|
+
const blocks = rules.map(
|
|
341
|
+
(rule) => `<system-reminder reason="stream_rule" rule="${rule.name.replace(/"/g, "'")}">
|
|
342
|
+
Your previous response was interrupted mid-stream because it violated this rule. The interrupted output was discarded: the user never saw it and no tool call from it ran. Regenerate the response from the start and follow the rule:
|
|
343
|
+
|
|
344
|
+
${rule.reminder.trim()}
|
|
345
|
+
</system-reminder>`
|
|
346
|
+
);
|
|
347
|
+
return {
|
|
348
|
+
role: "user",
|
|
349
|
+
content: blocks.join("\n\n"),
|
|
350
|
+
provenance: { source: "runtime", kind: "notification", visibility: "hidden" }
|
|
351
|
+
};
|
|
352
|
+
}
|
|
353
|
+
|
|
120
354
|
// src/agent-loop.ts
|
|
121
355
|
var DEFAULT_MAX_TURNS = 300;
|
|
122
356
|
var DEFAULT_TOOL_TIMEOUT_MS = 3e5;
|
|
357
|
+
var PREEMPT_GRACE_MS = 500;
|
|
358
|
+
var STEER_INTERRUPTED_TEXT = "Interrupted: the user sent a new message.";
|
|
359
|
+
var ATOMIC_TOOL_NAMES = /* @__PURE__ */ new Set(["edit", "write", "multi_edit", "apply_patch", "notebook_edit"]);
|
|
360
|
+
function isInterruptibleTool(tool, name) {
|
|
361
|
+
if (tool?.interruptible !== void 0) return tool.interruptible;
|
|
362
|
+
return !ATOMIC_TOOL_NAMES.has(name);
|
|
363
|
+
}
|
|
364
|
+
function markSteerInterrupted(record) {
|
|
365
|
+
const content = record.content;
|
|
366
|
+
let next;
|
|
367
|
+
if (typeof content === "string") {
|
|
368
|
+
next = content.trim() ? `${STEER_INTERRUPTED_TEXT}
|
|
369
|
+
Partial output:
|
|
370
|
+
${content}` : STEER_INTERRUPTED_TEXT;
|
|
371
|
+
} else {
|
|
372
|
+
next = [
|
|
373
|
+
{ type: "text", text: `${STEER_INTERRUPTED_TEXT}
|
|
374
|
+
Partial output:` },
|
|
375
|
+
...content
|
|
376
|
+
];
|
|
377
|
+
}
|
|
378
|
+
return { toolCallId: record.toolCallId, content: next, isError: true };
|
|
379
|
+
}
|
|
123
380
|
var _toolRedaction;
|
|
124
381
|
function toolRedactionOptions() {
|
|
125
|
-
_toolRedaction ??= { secrets: (0,
|
|
382
|
+
_toolRedaction ??= { secrets: (0, import_ai2.environmentSecrets)(process.env) };
|
|
126
383
|
return _toolRedaction;
|
|
127
384
|
}
|
|
128
385
|
var _diagFn = null;
|
|
@@ -205,7 +462,7 @@ function isBillingError(err) {
|
|
|
205
462
|
if (!(err instanceof Error)) return false;
|
|
206
463
|
const statusCode = err.statusCode;
|
|
207
464
|
if (statusCode === 402) return true;
|
|
208
|
-
return (0,
|
|
465
|
+
return (0, import_ai2.isHardBillingMessage)(err.message);
|
|
209
466
|
}
|
|
210
467
|
function isUsageLimitError(err) {
|
|
211
468
|
if (!(err instanceof Error)) return false;
|
|
@@ -430,6 +687,7 @@ async function* agentLoop(messages, options) {
|
|
|
430
687
|
let runawayToolcallRetries = 0;
|
|
431
688
|
let overflowCompactionAttempts = 0;
|
|
432
689
|
let toolResultTruncationAttempted = false;
|
|
690
|
+
const streamRuleMonitor = options.streamRules && options.streamRules.rules.length > 0 ? new StreamRuleMonitor(options.streamRules) : null;
|
|
433
691
|
const invalidToolArgumentCounts = /* @__PURE__ */ new Map();
|
|
434
692
|
let toolArgumentAutoContinueUsed = false;
|
|
435
693
|
let useNonStreamingFallback = false;
|
|
@@ -483,16 +741,7 @@ async function* agentLoop(messages, options) {
|
|
|
483
741
|
turn++;
|
|
484
742
|
if (logicalTurnStartedAt === 0) logicalTurnStartedAt = Date.now();
|
|
485
743
|
toolMap = new Map((options.tools ?? []).map((t) => [t.name, t]));
|
|
486
|
-
|
|
487
|
-
for (const m of messages) {
|
|
488
|
-
if (typeof m.content === "string") msgChars += m.content.length;
|
|
489
|
-
else if (Array.isArray(m.content)) {
|
|
490
|
-
for (const p of m.content) {
|
|
491
|
-
if ("text" in p && typeof p.text === "string") msgChars += p.text.length;
|
|
492
|
-
if ("content" in p && typeof p.content === "string") msgChars += p.content.length;
|
|
493
|
-
}
|
|
494
|
-
}
|
|
495
|
-
}
|
|
744
|
+
const msgChars = countMessageChars(messages);
|
|
496
745
|
let firstEventTimeoutMs;
|
|
497
746
|
let initialHardTimeoutMs;
|
|
498
747
|
if (baseFirstEventTimeoutMs === STREAM_FIRST_EVENT_TIMEOUT_MS) {
|
|
@@ -563,6 +812,9 @@ async function* agentLoop(messages, options) {
|
|
|
563
812
|
let toolcallNoProgressCount = 0;
|
|
564
813
|
let runawayDetected = null;
|
|
565
814
|
let attemptText = "";
|
|
815
|
+
let streamRuleHit = null;
|
|
816
|
+
let attemptThinkingChars = 0;
|
|
817
|
+
streamRuleMonitor?.beginAttempt();
|
|
566
818
|
let lastYieldEndTime = Date.now();
|
|
567
819
|
let maxConsumerLagMs = 0;
|
|
568
820
|
const forwardAbort = () => streamController.abort();
|
|
@@ -629,7 +881,7 @@ async function* agentLoop(messages, options) {
|
|
|
629
881
|
});
|
|
630
882
|
}
|
|
631
883
|
}
|
|
632
|
-
const result = (0,
|
|
884
|
+
const result = (0, import_ai2.stream)({
|
|
633
885
|
provider: options.provider,
|
|
634
886
|
model: options.model,
|
|
635
887
|
messages,
|
|
@@ -649,8 +901,11 @@ async function* agentLoop(messages, options) {
|
|
|
649
901
|
transportSessionId: options.transportSessionId,
|
|
650
902
|
projectId: liveProjectId,
|
|
651
903
|
cacheRetention: options.cacheRetention,
|
|
904
|
+
onContextPrepared: options.onContextPrepared,
|
|
652
905
|
promptCacheKey: options.promptCacheKey,
|
|
653
906
|
serviceTier: options.serviceTier,
|
|
907
|
+
responsesLite: options.responsesLite,
|
|
908
|
+
strictTools: options.strictTools,
|
|
654
909
|
supportsImages: options.supportsImages,
|
|
655
910
|
supportsVideo: options.supportsVideo,
|
|
656
911
|
compaction: options.compaction,
|
|
@@ -726,8 +981,14 @@ async function* agentLoop(messages, options) {
|
|
|
726
981
|
}
|
|
727
982
|
if (event.type === "text_delta") {
|
|
728
983
|
attemptText += event.text;
|
|
984
|
+
streamRuleHit = streamRuleMonitor?.checkText(event.text) ?? null;
|
|
985
|
+
if (streamRuleHit) {
|
|
986
|
+
streamController.abort();
|
|
987
|
+
break;
|
|
988
|
+
}
|
|
729
989
|
yield { type: "text_delta", text: event.text };
|
|
730
990
|
} else if (event.type === "thinking_delta") {
|
|
991
|
+
attemptThinkingChars += event.text.length;
|
|
731
992
|
yield { type: "thinking_delta", text: event.text };
|
|
732
993
|
} else if (event.type === "server_toolcall") {
|
|
733
994
|
yield {
|
|
@@ -748,6 +1009,11 @@ async function* agentLoop(messages, options) {
|
|
|
748
1009
|
toolcallDeltaChars += chunkChars;
|
|
749
1010
|
toolcallDeltaCount++;
|
|
750
1011
|
toolcallNoProgressCount = chunkChars > 0 ? 0 : toolcallNoProgressCount + 1;
|
|
1012
|
+
streamRuleHit = streamRuleMonitor?.checkToolArgs(event.id, event.name, event.argsJson ?? "") ?? null;
|
|
1013
|
+
if (streamRuleHit) {
|
|
1014
|
+
streamController.abort();
|
|
1015
|
+
break;
|
|
1016
|
+
}
|
|
751
1017
|
if (!runawayDetected && (toolcallDeltaChars > MAX_TOOLCALL_DELTA_CHARS || toolcallNoProgressCount > MAX_TOOLCALL_NO_PROGRESS_EVENTS)) {
|
|
752
1018
|
runawayDetected = {
|
|
753
1019
|
kind: toolcallDeltaChars > MAX_TOOLCALL_DELTA_CHARS ? "chars" : "events",
|
|
@@ -770,6 +1036,54 @@ async function* agentLoop(messages, options) {
|
|
|
770
1036
|
lastYieldEndTime = Date.now();
|
|
771
1037
|
resetIdleTimer();
|
|
772
1038
|
}
|
|
1039
|
+
if (streamRuleHit && streamRuleMonitor) {
|
|
1040
|
+
const attempt = streamRuleMonitor.recordTrigger(streamRuleHit.rules);
|
|
1041
|
+
const ruleNames = streamRuleHit.rules.map((rule) => rule.name);
|
|
1042
|
+
const priorPrompt = latestProviderUsage ? latestProviderUsage.inputTokens + (latestProviderUsage.cacheRead ?? 0) + (latestProviderUsage.cacheWrite ?? 0) : 0;
|
|
1043
|
+
const newChars = priorPrompt > 0 && usageAnchorIndex !== void 0 ? countMessageChars(messages.slice(usageAnchorIndex + 1)) : msgChars;
|
|
1044
|
+
const abortedUsage = {
|
|
1045
|
+
inputTokens: Math.ceil(newChars / 4),
|
|
1046
|
+
outputTokens: Math.ceil(
|
|
1047
|
+
(attemptText.length + attemptThinkingChars + toolcallDeltaChars) / 4
|
|
1048
|
+
),
|
|
1049
|
+
...priorPrompt > 0 ? { cacheRead: priorPrompt } : {}
|
|
1050
|
+
};
|
|
1051
|
+
totalUsage.inputTokens += abortedUsage.inputTokens;
|
|
1052
|
+
totalUsage.outputTokens += abortedUsage.outputTokens;
|
|
1053
|
+
if (abortedUsage.cacheRead) {
|
|
1054
|
+
totalUsage.cacheRead = (totalUsage.cacheRead ?? 0) + abortedUsage.cacheRead;
|
|
1055
|
+
}
|
|
1056
|
+
diag("stream_rule_triggered", {
|
|
1057
|
+
rules: ruleNames.join(","),
|
|
1058
|
+
source: streamRuleHit.source,
|
|
1059
|
+
toolName: streamRuleHit.toolName,
|
|
1060
|
+
attempt,
|
|
1061
|
+
maxAttempts: streamRuleMonitor.maxRetries,
|
|
1062
|
+
discardedChars: attemptText.length + toolcallDeltaChars,
|
|
1063
|
+
provider: options.provider,
|
|
1064
|
+
model: options.model
|
|
1065
|
+
});
|
|
1066
|
+
messages.push(buildStreamRuleReminder(streamRuleHit.rules));
|
|
1067
|
+
yield {
|
|
1068
|
+
type: "stream_rule_triggered",
|
|
1069
|
+
rules: ruleNames,
|
|
1070
|
+
source: streamRuleHit.source,
|
|
1071
|
+
...streamRuleHit.toolName !== void 0 ? { toolName: streamRuleHit.toolName } : {},
|
|
1072
|
+
attempt,
|
|
1073
|
+
maxAttempts: streamRuleMonitor.maxRetries,
|
|
1074
|
+
usage: abortedUsage
|
|
1075
|
+
};
|
|
1076
|
+
yield {
|
|
1077
|
+
type: "retry",
|
|
1078
|
+
reason: "stream_rule",
|
|
1079
|
+
attempt,
|
|
1080
|
+
maxAttempts: streamRuleMonitor.maxRetries,
|
|
1081
|
+
delayMs: 0,
|
|
1082
|
+
silent: true
|
|
1083
|
+
};
|
|
1084
|
+
turn--;
|
|
1085
|
+
continue;
|
|
1086
|
+
}
|
|
773
1087
|
diag("stream_done", {
|
|
774
1088
|
events: streamEventCount,
|
|
775
1089
|
totalMs: Date.now() - streamCallStart,
|
|
@@ -961,7 +1275,7 @@ async function* agentLoop(messages, options) {
|
|
|
961
1275
|
const detail = runawayDetected.kind === "chars" ? `${(runawayDetected.chars / 1024).toFixed(0)} KB of tool-call arguments` : `${runawayDetected.noProgressEvents} consecutive tool-call delta events without argument progress`;
|
|
962
1276
|
yield {
|
|
963
1277
|
type: "error",
|
|
964
|
-
error: new
|
|
1278
|
+
error: new import_ai2.EZCoderAIError(
|
|
965
1279
|
`The model repeatedly failed to close a tool call after ${MAX_RUNAWAY_TOOLCALL_RETRIES} automatic retries (${detail}). Your conversation is preserved.`,
|
|
966
1280
|
{
|
|
967
1281
|
source: "provider",
|
|
@@ -1043,7 +1357,7 @@ async function* agentLoop(messages, options) {
|
|
|
1043
1357
|
});
|
|
1044
1358
|
yield {
|
|
1045
1359
|
type: "error",
|
|
1046
|
-
error: new
|
|
1360
|
+
error: new import_ai2.EZCoderAIError(
|
|
1047
1361
|
`The connection to the API provider stopped responding after ${MAX_STALL_RETRIES} automatic retries. Your conversation is preserved.`,
|
|
1048
1362
|
{
|
|
1049
1363
|
source: "network",
|
|
@@ -1276,10 +1590,14 @@ async function* agentLoop(messages, options) {
|
|
|
1276
1590
|
fatalToolArgumentRecoverable = recoverable;
|
|
1277
1591
|
fatalToolArgumentToolName = toolName;
|
|
1278
1592
|
};
|
|
1593
|
+
const preempt = new AbortController();
|
|
1594
|
+
const unsubscribeSteering = options.onSteeringAvailable?.(() => preempt.abort());
|
|
1279
1595
|
const executionOptions = {
|
|
1280
1596
|
signal: options.signal,
|
|
1597
|
+
preemptSignal: options.onSteeringAvailable ? preempt.signal : void 0,
|
|
1281
1598
|
maxToolResultChars: options.maxToolResultChars,
|
|
1282
1599
|
maxTurnToolResultChars: options.maxTurnToolResultChars,
|
|
1600
|
+
transformToolResult: options.transformToolResult,
|
|
1283
1601
|
toolMap,
|
|
1284
1602
|
invalidToolArgumentCounts,
|
|
1285
1603
|
markFatalToolArgumentError,
|
|
@@ -1288,7 +1606,15 @@ async function* agentLoop(messages, options) {
|
|
|
1288
1606
|
const hasSequentialToolCall = toolCalls.some(
|
|
1289
1607
|
(toolCall) => toolMap.get(toolCall.name)?.executionMode === "sequential"
|
|
1290
1608
|
);
|
|
1291
|
-
|
|
1609
|
+
let executionResult;
|
|
1610
|
+
try {
|
|
1611
|
+
executionResult = hasSequentialToolCall ? yield* executeToolCallsMixed(toolCalls, toolResults, executionOptions) : yield* executeToolCallsParallel(toolCalls, toolResults, executionOptions);
|
|
1612
|
+
} finally {
|
|
1613
|
+
unsubscribeSteering?.();
|
|
1614
|
+
}
|
|
1615
|
+
if (executionResult.preempted) {
|
|
1616
|
+
diag("steer_preempted_tools", { turn, provider: options.provider, model: options.model });
|
|
1617
|
+
}
|
|
1292
1618
|
messages.push({ role: "tool", content: executionResult.toolResults });
|
|
1293
1619
|
yield { type: "checkpoint", turn };
|
|
1294
1620
|
const toolsAborted = executionResult.aborted;
|
|
@@ -1445,7 +1771,7 @@ async function executeSingleToolCall(toolCall, options, pushEvent) {
|
|
|
1445
1771
|
options.seenToolCalls.add(signature);
|
|
1446
1772
|
}
|
|
1447
1773
|
if (!tool) {
|
|
1448
|
-
resultContent =
|
|
1774
|
+
resultContent = unknownToolMessage(toolCall.name, [...options.toolMap.keys()]);
|
|
1449
1775
|
isError = true;
|
|
1450
1776
|
} else {
|
|
1451
1777
|
try {
|
|
@@ -1462,8 +1788,12 @@ async function executeSingleToolCall(toolCall, options, pushEvent) {
|
|
|
1462
1788
|
}
|
|
1463
1789
|
const callerSignal = options.signal;
|
|
1464
1790
|
const toolTimeout = AbortSignal.timeout(tool.timeoutMs ?? DEFAULT_TOOL_TIMEOUT_MS);
|
|
1791
|
+
const preemptSignal = isInterruptibleTool(tool, toolCall.name) ? options.preemptSignal : void 0;
|
|
1792
|
+
const toolSignals = [callerSignal, toolTimeout, preemptSignal].filter(
|
|
1793
|
+
(s) => s !== void 0
|
|
1794
|
+
);
|
|
1465
1795
|
const ctx = {
|
|
1466
|
-
signal:
|
|
1796
|
+
signal: toolSignals.length === 1 ? toolTimeout : AbortSignal.any(toolSignals),
|
|
1467
1797
|
toolCallId: toolCall.id,
|
|
1468
1798
|
onUpdate: (update) => {
|
|
1469
1799
|
pushEvent({
|
|
@@ -1473,13 +1803,25 @@ async function executeSingleToolCall(toolCall, options, pushEvent) {
|
|
|
1473
1803
|
});
|
|
1474
1804
|
}
|
|
1475
1805
|
};
|
|
1806
|
+
const hardSignals = [callerSignal, toolTimeout].filter(
|
|
1807
|
+
(s) => s !== void 0
|
|
1808
|
+
);
|
|
1476
1809
|
const raw = await abortablePromise(
|
|
1477
1810
|
Promise.resolve().then(() => tool.execute(parsed, ctx)),
|
|
1478
|
-
|
|
1811
|
+
hardSignals.length === 1 ? toolTimeout : AbortSignal.any(hardSignals)
|
|
1479
1812
|
);
|
|
1480
1813
|
const normalized = normalizeToolResult(raw);
|
|
1481
|
-
resultContent = (0,
|
|
1482
|
-
details = (0,
|
|
1814
|
+
resultContent = (0, import_ai2.redactValue)(normalized.content, toolRedactionOptions());
|
|
1815
|
+
details = (0, import_ai2.redactValue)(normalized.details, toolRedactionOptions());
|
|
1816
|
+
if (options.transformToolResult) {
|
|
1817
|
+
try {
|
|
1818
|
+
resultContent = options.transformToolResult(
|
|
1819
|
+
{ name: toolCall.name, args: toolCall.args },
|
|
1820
|
+
resultContent
|
|
1821
|
+
);
|
|
1822
|
+
} catch {
|
|
1823
|
+
}
|
|
1824
|
+
}
|
|
1483
1825
|
for (const key of options.invalidToolArgumentCounts.keys()) {
|
|
1484
1826
|
if (key.startsWith(`${toolCall.name}:`)) options.invalidToolArgumentCounts.delete(key);
|
|
1485
1827
|
}
|
|
@@ -1491,12 +1833,14 @@ async function executeSingleToolCall(toolCall, options, pushEvent) {
|
|
|
1491
1833
|
const failureCount = (options.invalidToolArgumentCounts.get(failureKey) ?? 0) + 1;
|
|
1492
1834
|
options.invalidToolArgumentCounts.set(failureKey, failureCount);
|
|
1493
1835
|
invalidArgAttempt = failureCount;
|
|
1836
|
+
const hints = argumentHints(tool, toolCall.args);
|
|
1494
1837
|
resultContent = `Invalid arguments for tool \`${toolCall.name}\`:
|
|
1495
|
-
` + prettyError +
|
|
1838
|
+
` + prettyError + (hints ? `
|
|
1839
|
+
${hints}` : "") + "\nRe-issue the call with each field as the correct type.";
|
|
1496
1840
|
if (failureCount >= 3) {
|
|
1497
1841
|
const recoverable = Object.keys(toolCall.args ?? {}).length === 0;
|
|
1498
1842
|
options.markFatalToolArgumentError(
|
|
1499
|
-
new
|
|
1843
|
+
new import_ai2.EZCoderAIError(
|
|
1500
1844
|
`The model repeatedly issued invalid arguments for tool \`${toolCall.name}\`. This is usually an upstream model/tool-calling bug` + (recoverable ? " (the provider's stream returned empty tool-call arguments)" : "") + `. Your conversation is preserved; send another message or switch models to continue.`,
|
|
1501
1845
|
{
|
|
1502
1846
|
source: "provider",
|
|
@@ -1510,15 +1854,24 @@ async function executeSingleToolCall(toolCall, options, pushEvent) {
|
|
|
1510
1854
|
} else if (options.signal?.aborted && isAbortError(err)) {
|
|
1511
1855
|
resultContent = indeterminateOutcomeText(toolCall.name);
|
|
1512
1856
|
} else {
|
|
1513
|
-
resultContent = (0,
|
|
1857
|
+
resultContent = (0, import_ai2.redactValue)(
|
|
1514
1858
|
err instanceof Error ? err.message : String(err),
|
|
1515
1859
|
toolRedactionOptions()
|
|
1516
1860
|
);
|
|
1517
1861
|
}
|
|
1518
1862
|
}
|
|
1519
1863
|
}
|
|
1520
|
-
resultContent = (0,
|
|
1521
|
-
details = (0,
|
|
1864
|
+
resultContent = (0, import_ai2.redactValue)(resultContent, toolRedactionOptions());
|
|
1865
|
+
details = (0, import_ai2.redactValue)(details, toolRedactionOptions());
|
|
1866
|
+
if (options.preemptSignal?.aborted && isInterruptibleTool(options.toolMap.get(toolCall.name), toolCall.name)) {
|
|
1867
|
+
const marked = markSteerInterrupted({
|
|
1868
|
+
toolCallId: toolCall.id,
|
|
1869
|
+
content: resultContent,
|
|
1870
|
+
isError
|
|
1871
|
+
});
|
|
1872
|
+
resultContent = marked.content;
|
|
1873
|
+
isError = true;
|
|
1874
|
+
}
|
|
1522
1875
|
const durationMs = Date.now() - startTime;
|
|
1523
1876
|
pushEvent({
|
|
1524
1877
|
type: "tool_call_end",
|
|
@@ -1531,13 +1884,67 @@ async function executeSingleToolCall(toolCall, options, pushEvent) {
|
|
|
1531
1884
|
});
|
|
1532
1885
|
return { toolCallId: toolCall.id, content: resultContent, isError };
|
|
1533
1886
|
}
|
|
1887
|
+
function createPreemptTracker(options, eventStream, state, resultsById, dispatchedIds) {
|
|
1888
|
+
const running = /* @__PURE__ */ new Map();
|
|
1889
|
+
let graceTimer;
|
|
1890
|
+
const preemptSignal = options.preemptSignal;
|
|
1891
|
+
const abandon = () => {
|
|
1892
|
+
graceTimer = void 0;
|
|
1893
|
+
for (const [id, entry] of running) {
|
|
1894
|
+
if (!entry.interruptible || resultsById.has(id)) continue;
|
|
1895
|
+
resultsById.set(id, { toolCallId: id, content: STEER_INTERRUPTED_TEXT, isError: true });
|
|
1896
|
+
pushToolEvent(eventStream, state, {
|
|
1897
|
+
type: "tool_call_end",
|
|
1898
|
+
toolCallId: id,
|
|
1899
|
+
result: STEER_INTERRUPTED_TEXT,
|
|
1900
|
+
isError: true,
|
|
1901
|
+
durationMs: Date.now() - entry.startedAt
|
|
1902
|
+
});
|
|
1903
|
+
running.delete(id);
|
|
1904
|
+
}
|
|
1905
|
+
if (running.size === 0) eventStream.close();
|
|
1906
|
+
};
|
|
1907
|
+
const onPreempt = () => {
|
|
1908
|
+
if (graceTimer === void 0) graceTimer = setTimeout(abandon, PREEMPT_GRACE_MS);
|
|
1909
|
+
};
|
|
1910
|
+
preemptSignal?.addEventListener("abort", onPreempt, { once: true });
|
|
1911
|
+
return {
|
|
1912
|
+
async run(toolCall) {
|
|
1913
|
+
if (options.preemptSignal?.aborted) return;
|
|
1914
|
+
dispatchedIds.add(toolCall.id);
|
|
1915
|
+
const interruptible = isInterruptibleTool(options.toolMap.get(toolCall.name), toolCall.name);
|
|
1916
|
+
running.set(toolCall.id, { toolCall, interruptible, startedAt: Date.now() });
|
|
1917
|
+
const record = await executeSingleToolCall(
|
|
1918
|
+
toolCall,
|
|
1919
|
+
options,
|
|
1920
|
+
(event) => pushToolEvent(eventStream, state, event)
|
|
1921
|
+
);
|
|
1922
|
+
running.delete(toolCall.id);
|
|
1923
|
+
if (resultsById.has(toolCall.id)) return;
|
|
1924
|
+
const alreadyMarked = typeof record.content === "string" ? record.content.startsWith(STEER_INTERRUPTED_TEXT) : record.content[0]?.type === "text" && record.content[0].text.startsWith(STEER_INTERRUPTED_TEXT);
|
|
1925
|
+
resultsById.set(
|
|
1926
|
+
toolCall.id,
|
|
1927
|
+
interruptible && preemptSignal?.aborted && !alreadyMarked ? markSteerInterrupted(record) : record
|
|
1928
|
+
);
|
|
1929
|
+
if (preemptSignal?.aborted && graceTimer === void 0 && running.size === 0) {
|
|
1930
|
+
eventStream.close();
|
|
1931
|
+
}
|
|
1932
|
+
},
|
|
1933
|
+
preempted: () => preemptSignal?.aborted === true,
|
|
1934
|
+
dispose: () => {
|
|
1935
|
+
if (graceTimer !== void 0) clearTimeout(graceTimer);
|
|
1936
|
+
preemptSignal?.removeEventListener("abort", onPreempt);
|
|
1937
|
+
}
|
|
1938
|
+
};
|
|
1939
|
+
}
|
|
1534
1940
|
async function* executeToolCallsMixed(toolCalls, initialToolResults, options) {
|
|
1535
|
-
const eventStream = new
|
|
1941
|
+
const eventStream = new import_ai2.EventStream();
|
|
1536
1942
|
const state = { finalized: false };
|
|
1537
1943
|
const resultsById = /* @__PURE__ */ new Map();
|
|
1538
1944
|
const dispatchedIds = /* @__PURE__ */ new Set();
|
|
1539
1945
|
const abortHandler = () => eventStream.abort(new Error("aborted"));
|
|
1540
1946
|
options.signal?.addEventListener("abort", abortHandler, { once: true });
|
|
1947
|
+
const tracker = createPreemptTracker(options, eventStream, state, resultsById, dispatchedIds);
|
|
1541
1948
|
const phases = [];
|
|
1542
1949
|
let currentParallel = [];
|
|
1543
1950
|
for (const toolCall of toolCalls) {
|
|
@@ -1558,40 +1965,18 @@ async function* executeToolCallsMixed(toolCalls, initialToolResults, options) {
|
|
|
1558
1965
|
void (async () => {
|
|
1559
1966
|
try {
|
|
1560
1967
|
for (const phase of phases) {
|
|
1561
|
-
if (options.signal?.aborted) break;
|
|
1968
|
+
if (options.signal?.aborted || options.preemptSignal?.aborted) break;
|
|
1562
1969
|
if (phase.sequential) {
|
|
1563
1970
|
const signature = JSON.stringify([
|
|
1564
1971
|
phase.sequential.name,
|
|
1565
1972
|
canonicalToolArgs(phase.sequential.args)
|
|
1566
1973
|
]);
|
|
1567
1974
|
if (!options.seenToolCalls.has(signature)) options.seenToolCalls.clear();
|
|
1568
|
-
|
|
1569
|
-
const record = await executeSingleToolCall(
|
|
1570
|
-
phase.sequential,
|
|
1571
|
-
options,
|
|
1572
|
-
(event) => pushToolEvent(eventStream, state, event)
|
|
1573
|
-
);
|
|
1574
|
-
resultsById.set(record.toolCallId, record);
|
|
1975
|
+
await tracker.run(phase.sequential);
|
|
1575
1976
|
} else if (phase.parallel.length === 1) {
|
|
1576
|
-
|
|
1577
|
-
const record = await executeSingleToolCall(
|
|
1578
|
-
phase.parallel[0],
|
|
1579
|
-
options,
|
|
1580
|
-
(event) => pushToolEvent(eventStream, state, event)
|
|
1581
|
-
);
|
|
1582
|
-
resultsById.set(record.toolCallId, record);
|
|
1977
|
+
await tracker.run(phase.parallel[0]);
|
|
1583
1978
|
} else {
|
|
1584
|
-
await Promise.all(
|
|
1585
|
-
phase.parallel.map(async (toolCall) => {
|
|
1586
|
-
dispatchedIds.add(toolCall.id);
|
|
1587
|
-
const record = await executeSingleToolCall(
|
|
1588
|
-
toolCall,
|
|
1589
|
-
options,
|
|
1590
|
-
(event) => pushToolEvent(eventStream, state, event)
|
|
1591
|
-
);
|
|
1592
|
-
resultsById.set(record.toolCallId, record);
|
|
1593
|
-
})
|
|
1594
|
-
);
|
|
1979
|
+
await Promise.all(phase.parallel.map((toolCall) => tracker.run(toolCall)));
|
|
1595
1980
|
}
|
|
1596
1981
|
}
|
|
1597
1982
|
if (!state.finalized) eventStream.close();
|
|
@@ -1612,31 +1997,23 @@ async function* executeToolCallsMixed(toolCalls, initialToolResults, options) {
|
|
|
1612
1997
|
}
|
|
1613
1998
|
} finally {
|
|
1614
1999
|
options.signal?.removeEventListener("abort", abortHandler);
|
|
2000
|
+
tracker.dispose();
|
|
1615
2001
|
state.finalized = true;
|
|
1616
2002
|
}
|
|
1617
2003
|
const toolResults = buildToolResults(initialToolResults, toolCalls, resultsById, dispatchedIds);
|
|
1618
2004
|
capToolResults(toolResults, options.maxToolResultChars);
|
|
1619
2005
|
capTurnToolResults(toolResults, options.maxTurnToolResultChars);
|
|
1620
|
-
return { toolResults, aborted };
|
|
2006
|
+
return { toolResults, aborted, preempted: !aborted && tracker.preempted() };
|
|
1621
2007
|
}
|
|
1622
2008
|
async function* executeToolCallsParallel(toolCalls, initialToolResults, options) {
|
|
1623
|
-
const eventStream = new
|
|
2009
|
+
const eventStream = new import_ai2.EventStream();
|
|
1624
2010
|
const state = { finalized: false };
|
|
1625
2011
|
const resultsById = /* @__PURE__ */ new Map();
|
|
1626
2012
|
const dispatchedIds = /* @__PURE__ */ new Set();
|
|
1627
2013
|
const abortHandler = () => eventStream.abort(new Error("aborted"));
|
|
1628
2014
|
options.signal?.addEventListener("abort", abortHandler, { once: true });
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
dispatchedIds.add(toolCall.id);
|
|
1632
|
-
const record = await executeSingleToolCall(
|
|
1633
|
-
toolCall,
|
|
1634
|
-
options,
|
|
1635
|
-
(event) => pushToolEvent(eventStream, state, event)
|
|
1636
|
-
);
|
|
1637
|
-
resultsById.set(record.toolCallId, record);
|
|
1638
|
-
})
|
|
1639
|
-
).then(() => {
|
|
2015
|
+
const tracker = createPreemptTracker(options, eventStream, state, resultsById, dispatchedIds);
|
|
2016
|
+
Promise.all(toolCalls.map((toolCall) => tracker.run(toolCall))).then(() => {
|
|
1640
2017
|
if (!state.finalized) eventStream.close();
|
|
1641
2018
|
}).catch((err) => {
|
|
1642
2019
|
if (!state.finalized) eventStream.abort(err instanceof Error ? err : new Error(String(err)));
|
|
@@ -1654,12 +2031,13 @@ async function* executeToolCallsParallel(toolCalls, initialToolResults, options)
|
|
|
1654
2031
|
}
|
|
1655
2032
|
} finally {
|
|
1656
2033
|
options.signal?.removeEventListener("abort", abortHandler);
|
|
2034
|
+
tracker.dispose();
|
|
1657
2035
|
state.finalized = true;
|
|
1658
2036
|
}
|
|
1659
2037
|
const toolResults = buildToolResults(initialToolResults, toolCalls, resultsById, dispatchedIds);
|
|
1660
2038
|
capToolResults(toolResults, options.maxToolResultChars);
|
|
1661
2039
|
capTurnToolResults(toolResults, options.maxTurnToolResultChars);
|
|
1662
|
-
return { toolResults, aborted };
|
|
2040
|
+
return { toolResults, aborted, preempted: !aborted && tracker.preempted() };
|
|
1663
2041
|
}
|
|
1664
2042
|
function cancelledBeforeStartText(name) {
|
|
1665
2043
|
return `\`${name}\` was cancelled before it started, so it had no effect. Safe to retry.`;
|
|
@@ -1699,8 +2077,8 @@ function capToolResults(toolResults, maxToolResultChars) {
|
|
|
1699
2077
|
const originalChars = toolResult.content.length;
|
|
1700
2078
|
const headChars = Math.floor(max * 0.7);
|
|
1701
2079
|
const tailChars = max - headChars;
|
|
1702
|
-
const head = (0,
|
|
1703
|
-
const tail = (0,
|
|
2080
|
+
const head = (0, import_ai2.sliceHead)(toolResult.content, headChars);
|
|
2081
|
+
const tail = (0, import_ai2.sliceTail)(toolResult.content, tailChars);
|
|
1704
2082
|
const omitted = originalChars - head.length - tail.length;
|
|
1705
2083
|
toolResult.content = head + `
|
|
1706
2084
|
|
|
@@ -1736,11 +2114,11 @@ function capTurnToolResults(toolResults, maxTurnToolResultChars) {
|
|
|
1736
2114
|
const headChars = Math.floor(fairShare * 0.7);
|
|
1737
2115
|
const tailChars = fairShare - headChars;
|
|
1738
2116
|
const omitted = originalChars - fairShare;
|
|
1739
|
-
toolResult.content = (0,
|
|
2117
|
+
toolResult.content = (0, import_ai2.sliceHead)(toolResult.content, headChars) + `
|
|
1740
2118
|
|
|
1741
2119
|
[... ${omitted} characters trimmed: this turn's combined tool results exceeded the per-turn budget. Re-run this call alone with narrower filters or offset/limit if you need the omitted content ...]
|
|
1742
2120
|
|
|
1743
|
-
` + (0,
|
|
2121
|
+
` + (0, import_ai2.sliceTail)(toolResult.content, tailChars);
|
|
1744
2122
|
toolResult.capped = {
|
|
1745
2123
|
originalChars: toolResult.capped?.originalChars ?? originalChars,
|
|
1746
2124
|
keptChars: toolResult.content.length,
|
|
@@ -1759,8 +2137,8 @@ function truncateToolResultText(text, maxChars) {
|
|
|
1759
2137
|
if (text.length <= maxChars) return text;
|
|
1760
2138
|
const tailChars = Math.min(Math.floor(maxChars * 0.3), 2e4);
|
|
1761
2139
|
const headChars = Math.max(maxChars - tailChars, 0);
|
|
1762
|
-
const head = (0,
|
|
1763
|
-
const tail = (0,
|
|
2140
|
+
const head = (0, import_ai2.sliceHead)(text, headChars);
|
|
2141
|
+
const tail = (0, import_ai2.sliceTail)(text, tailChars);
|
|
1764
2142
|
const omitted = text.length - head.length - tail.length;
|
|
1765
2143
|
return `${head}
|
|
1766
2144
|
|
|
@@ -1826,6 +2204,19 @@ function sanitizeOrphanedServerTools(messages) {
|
|
|
1826
2204
|
break;
|
|
1827
2205
|
}
|
|
1828
2206
|
}
|
|
2207
|
+
function countMessageChars(messages) {
|
|
2208
|
+
let chars = 0;
|
|
2209
|
+
for (const m of messages) {
|
|
2210
|
+
if (typeof m.content === "string") chars += m.content.length;
|
|
2211
|
+
else if (Array.isArray(m.content)) {
|
|
2212
|
+
for (const p of m.content) {
|
|
2213
|
+
if ("text" in p && typeof p.text === "string") chars += p.text.length;
|
|
2214
|
+
if ("content" in p && typeof p.content === "string") chars += p.content.length;
|
|
2215
|
+
}
|
|
2216
|
+
}
|
|
2217
|
+
}
|
|
2218
|
+
return chars;
|
|
2219
|
+
}
|
|
1829
2220
|
function repairToolPairingAdjacent(messages) {
|
|
1830
2221
|
for (let i = 0; i < messages.length; i++) {
|
|
1831
2222
|
const msg = messages[i];
|
|
@@ -1896,7 +2287,7 @@ var AgentStream = class {
|
|
|
1896
2287
|
rejectResult;
|
|
1897
2288
|
hasConsumer = false;
|
|
1898
2289
|
constructor(generator, onDone) {
|
|
1899
|
-
this.events = new
|
|
2290
|
+
this.events = new import_ai3.EventStream();
|
|
1900
2291
|
this.resultPromise = new Promise((resolve, reject) => {
|
|
1901
2292
|
this.resolveResult = resolve;
|
|
1902
2293
|
this.rejectResult = reject;
|
|
@@ -1942,6 +2333,7 @@ var Agent = class {
|
|
|
1942
2333
|
options;
|
|
1943
2334
|
steeringQueue = [];
|
|
1944
2335
|
followUpQueue = [];
|
|
2336
|
+
steeringListeners = /* @__PURE__ */ new Set();
|
|
1945
2337
|
constructor(options) {
|
|
1946
2338
|
this.options = options;
|
|
1947
2339
|
if (options.system) {
|
|
@@ -1966,9 +2358,14 @@ var Agent = class {
|
|
|
1966
2358
|
get running() {
|
|
1967
2359
|
return this._running;
|
|
1968
2360
|
}
|
|
1969
|
-
/**
|
|
2361
|
+
/**
|
|
2362
|
+
* Queue a steering message. Instant interrupt: if tools are running, the
|
|
2363
|
+
* interruptible ones are preempted right away and the message is injected
|
|
2364
|
+
* before the next model call (the turn continues — unlike abort/Stop).
|
|
2365
|
+
*/
|
|
1970
2366
|
steer(msg) {
|
|
1971
2367
|
this.steeringQueue.push(msg);
|
|
2368
|
+
for (const listener of [...this.steeringListeners]) listener();
|
|
1972
2369
|
}
|
|
1973
2370
|
/** Queue a follow-up message for injection when the agent would otherwise stop. */
|
|
1974
2371
|
followUp(msg) {
|
|
@@ -1988,6 +2385,14 @@ var Agent = class {
|
|
|
1988
2385
|
const all = [...callerResult ?? [], ...queued];
|
|
1989
2386
|
return all.length > 0 ? all : null;
|
|
1990
2387
|
},
|
|
2388
|
+
onSteeringAvailable: (listener) => {
|
|
2389
|
+
this.steeringListeners.add(listener);
|
|
2390
|
+
const unsubscribeCaller = this.options.onSteeringAvailable?.(listener);
|
|
2391
|
+
return () => {
|
|
2392
|
+
this.steeringListeners.delete(listener);
|
|
2393
|
+
unsubscribeCaller?.();
|
|
2394
|
+
};
|
|
2395
|
+
},
|
|
1991
2396
|
getFollowUpMessages: async () => {
|
|
1992
2397
|
const callerResult = await this.options.getFollowUpMessages?.() ?? [];
|
|
1993
2398
|
const queued = this.followUpQueue.splice(0);
|
|
@@ -2005,7 +2410,12 @@ var Agent = class {
|
|
|
2005
2410
|
0 && (module.exports = {
|
|
2006
2411
|
Agent,
|
|
2007
2412
|
AgentStream,
|
|
2413
|
+
DEFAULT_STREAM_RULE_MAX_RETRIES,
|
|
2414
|
+
DEFAULT_STREAM_RULE_WINDOW_CHARS,
|
|
2415
|
+
JsonEscapeDecoder,
|
|
2416
|
+
StreamRuleMonitor,
|
|
2008
2417
|
agentLoop,
|
|
2418
|
+
buildStreamRuleReminder,
|
|
2009
2419
|
cancelledBeforeStartText,
|
|
2010
2420
|
indeterminateOutcomeText,
|
|
2011
2421
|
isAbortError,
|
|
@@ -2013,6 +2423,7 @@ var Agent = class {
|
|
|
2013
2423
|
isContextOverflow,
|
|
2014
2424
|
isLocalBackendUrl,
|
|
2015
2425
|
isUsageLimitError,
|
|
2426
|
+
repairToolPairingAdjacent,
|
|
2016
2427
|
setStreamDiagnostic
|
|
2017
2428
|
});
|
|
2018
2429
|
//# sourceMappingURL=index.cjs.map
|