@prestyj/agent 5.28.0 → 5.29.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +486 -77
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +141 -5
- package/dist/index.d.ts +141 -5
- package/dist/index.js +458 -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,6 +901,7 @@ 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,
|
|
654
907
|
supportsImages: options.supportsImages,
|
|
@@ -726,8 +979,14 @@ async function* agentLoop(messages, options) {
|
|
|
726
979
|
}
|
|
727
980
|
if (event.type === "text_delta") {
|
|
728
981
|
attemptText += event.text;
|
|
982
|
+
streamRuleHit = streamRuleMonitor?.checkText(event.text) ?? null;
|
|
983
|
+
if (streamRuleHit) {
|
|
984
|
+
streamController.abort();
|
|
985
|
+
break;
|
|
986
|
+
}
|
|
729
987
|
yield { type: "text_delta", text: event.text };
|
|
730
988
|
} else if (event.type === "thinking_delta") {
|
|
989
|
+
attemptThinkingChars += event.text.length;
|
|
731
990
|
yield { type: "thinking_delta", text: event.text };
|
|
732
991
|
} else if (event.type === "server_toolcall") {
|
|
733
992
|
yield {
|
|
@@ -748,6 +1007,11 @@ async function* agentLoop(messages, options) {
|
|
|
748
1007
|
toolcallDeltaChars += chunkChars;
|
|
749
1008
|
toolcallDeltaCount++;
|
|
750
1009
|
toolcallNoProgressCount = chunkChars > 0 ? 0 : toolcallNoProgressCount + 1;
|
|
1010
|
+
streamRuleHit = streamRuleMonitor?.checkToolArgs(event.id, event.name, event.argsJson ?? "") ?? null;
|
|
1011
|
+
if (streamRuleHit) {
|
|
1012
|
+
streamController.abort();
|
|
1013
|
+
break;
|
|
1014
|
+
}
|
|
751
1015
|
if (!runawayDetected && (toolcallDeltaChars > MAX_TOOLCALL_DELTA_CHARS || toolcallNoProgressCount > MAX_TOOLCALL_NO_PROGRESS_EVENTS)) {
|
|
752
1016
|
runawayDetected = {
|
|
753
1017
|
kind: toolcallDeltaChars > MAX_TOOLCALL_DELTA_CHARS ? "chars" : "events",
|
|
@@ -770,6 +1034,54 @@ async function* agentLoop(messages, options) {
|
|
|
770
1034
|
lastYieldEndTime = Date.now();
|
|
771
1035
|
resetIdleTimer();
|
|
772
1036
|
}
|
|
1037
|
+
if (streamRuleHit && streamRuleMonitor) {
|
|
1038
|
+
const attempt = streamRuleMonitor.recordTrigger(streamRuleHit.rules);
|
|
1039
|
+
const ruleNames = streamRuleHit.rules.map((rule) => rule.name);
|
|
1040
|
+
const priorPrompt = latestProviderUsage ? latestProviderUsage.inputTokens + (latestProviderUsage.cacheRead ?? 0) + (latestProviderUsage.cacheWrite ?? 0) : 0;
|
|
1041
|
+
const newChars = priorPrompt > 0 && usageAnchorIndex !== void 0 ? countMessageChars(messages.slice(usageAnchorIndex + 1)) : msgChars;
|
|
1042
|
+
const abortedUsage = {
|
|
1043
|
+
inputTokens: Math.ceil(newChars / 4),
|
|
1044
|
+
outputTokens: Math.ceil(
|
|
1045
|
+
(attemptText.length + attemptThinkingChars + toolcallDeltaChars) / 4
|
|
1046
|
+
),
|
|
1047
|
+
...priorPrompt > 0 ? { cacheRead: priorPrompt } : {}
|
|
1048
|
+
};
|
|
1049
|
+
totalUsage.inputTokens += abortedUsage.inputTokens;
|
|
1050
|
+
totalUsage.outputTokens += abortedUsage.outputTokens;
|
|
1051
|
+
if (abortedUsage.cacheRead) {
|
|
1052
|
+
totalUsage.cacheRead = (totalUsage.cacheRead ?? 0) + abortedUsage.cacheRead;
|
|
1053
|
+
}
|
|
1054
|
+
diag("stream_rule_triggered", {
|
|
1055
|
+
rules: ruleNames.join(","),
|
|
1056
|
+
source: streamRuleHit.source,
|
|
1057
|
+
toolName: streamRuleHit.toolName,
|
|
1058
|
+
attempt,
|
|
1059
|
+
maxAttempts: streamRuleMonitor.maxRetries,
|
|
1060
|
+
discardedChars: attemptText.length + toolcallDeltaChars,
|
|
1061
|
+
provider: options.provider,
|
|
1062
|
+
model: options.model
|
|
1063
|
+
});
|
|
1064
|
+
messages.push(buildStreamRuleReminder(streamRuleHit.rules));
|
|
1065
|
+
yield {
|
|
1066
|
+
type: "stream_rule_triggered",
|
|
1067
|
+
rules: ruleNames,
|
|
1068
|
+
source: streamRuleHit.source,
|
|
1069
|
+
...streamRuleHit.toolName !== void 0 ? { toolName: streamRuleHit.toolName } : {},
|
|
1070
|
+
attempt,
|
|
1071
|
+
maxAttempts: streamRuleMonitor.maxRetries,
|
|
1072
|
+
usage: abortedUsage
|
|
1073
|
+
};
|
|
1074
|
+
yield {
|
|
1075
|
+
type: "retry",
|
|
1076
|
+
reason: "stream_rule",
|
|
1077
|
+
attempt,
|
|
1078
|
+
maxAttempts: streamRuleMonitor.maxRetries,
|
|
1079
|
+
delayMs: 0,
|
|
1080
|
+
silent: true
|
|
1081
|
+
};
|
|
1082
|
+
turn--;
|
|
1083
|
+
continue;
|
|
1084
|
+
}
|
|
773
1085
|
diag("stream_done", {
|
|
774
1086
|
events: streamEventCount,
|
|
775
1087
|
totalMs: Date.now() - streamCallStart,
|
|
@@ -961,7 +1273,7 @@ async function* agentLoop(messages, options) {
|
|
|
961
1273
|
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
1274
|
yield {
|
|
963
1275
|
type: "error",
|
|
964
|
-
error: new
|
|
1276
|
+
error: new import_ai2.EZCoderAIError(
|
|
965
1277
|
`The model repeatedly failed to close a tool call after ${MAX_RUNAWAY_TOOLCALL_RETRIES} automatic retries (${detail}). Your conversation is preserved.`,
|
|
966
1278
|
{
|
|
967
1279
|
source: "provider",
|
|
@@ -1043,7 +1355,7 @@ async function* agentLoop(messages, options) {
|
|
|
1043
1355
|
});
|
|
1044
1356
|
yield {
|
|
1045
1357
|
type: "error",
|
|
1046
|
-
error: new
|
|
1358
|
+
error: new import_ai2.EZCoderAIError(
|
|
1047
1359
|
`The connection to the API provider stopped responding after ${MAX_STALL_RETRIES} automatic retries. Your conversation is preserved.`,
|
|
1048
1360
|
{
|
|
1049
1361
|
source: "network",
|
|
@@ -1276,10 +1588,14 @@ async function* agentLoop(messages, options) {
|
|
|
1276
1588
|
fatalToolArgumentRecoverable = recoverable;
|
|
1277
1589
|
fatalToolArgumentToolName = toolName;
|
|
1278
1590
|
};
|
|
1591
|
+
const preempt = new AbortController();
|
|
1592
|
+
const unsubscribeSteering = options.onSteeringAvailable?.(() => preempt.abort());
|
|
1279
1593
|
const executionOptions = {
|
|
1280
1594
|
signal: options.signal,
|
|
1595
|
+
preemptSignal: options.onSteeringAvailable ? preempt.signal : void 0,
|
|
1281
1596
|
maxToolResultChars: options.maxToolResultChars,
|
|
1282
1597
|
maxTurnToolResultChars: options.maxTurnToolResultChars,
|
|
1598
|
+
transformToolResult: options.transformToolResult,
|
|
1283
1599
|
toolMap,
|
|
1284
1600
|
invalidToolArgumentCounts,
|
|
1285
1601
|
markFatalToolArgumentError,
|
|
@@ -1288,7 +1604,15 @@ async function* agentLoop(messages, options) {
|
|
|
1288
1604
|
const hasSequentialToolCall = toolCalls.some(
|
|
1289
1605
|
(toolCall) => toolMap.get(toolCall.name)?.executionMode === "sequential"
|
|
1290
1606
|
);
|
|
1291
|
-
|
|
1607
|
+
let executionResult;
|
|
1608
|
+
try {
|
|
1609
|
+
executionResult = hasSequentialToolCall ? yield* executeToolCallsMixed(toolCalls, toolResults, executionOptions) : yield* executeToolCallsParallel(toolCalls, toolResults, executionOptions);
|
|
1610
|
+
} finally {
|
|
1611
|
+
unsubscribeSteering?.();
|
|
1612
|
+
}
|
|
1613
|
+
if (executionResult.preempted) {
|
|
1614
|
+
diag("steer_preempted_tools", { turn, provider: options.provider, model: options.model });
|
|
1615
|
+
}
|
|
1292
1616
|
messages.push({ role: "tool", content: executionResult.toolResults });
|
|
1293
1617
|
yield { type: "checkpoint", turn };
|
|
1294
1618
|
const toolsAborted = executionResult.aborted;
|
|
@@ -1445,7 +1769,7 @@ async function executeSingleToolCall(toolCall, options, pushEvent) {
|
|
|
1445
1769
|
options.seenToolCalls.add(signature);
|
|
1446
1770
|
}
|
|
1447
1771
|
if (!tool) {
|
|
1448
|
-
resultContent =
|
|
1772
|
+
resultContent = unknownToolMessage(toolCall.name, [...options.toolMap.keys()]);
|
|
1449
1773
|
isError = true;
|
|
1450
1774
|
} else {
|
|
1451
1775
|
try {
|
|
@@ -1462,8 +1786,12 @@ async function executeSingleToolCall(toolCall, options, pushEvent) {
|
|
|
1462
1786
|
}
|
|
1463
1787
|
const callerSignal = options.signal;
|
|
1464
1788
|
const toolTimeout = AbortSignal.timeout(tool.timeoutMs ?? DEFAULT_TOOL_TIMEOUT_MS);
|
|
1789
|
+
const preemptSignal = isInterruptibleTool(tool, toolCall.name) ? options.preemptSignal : void 0;
|
|
1790
|
+
const toolSignals = [callerSignal, toolTimeout, preemptSignal].filter(
|
|
1791
|
+
(s) => s !== void 0
|
|
1792
|
+
);
|
|
1465
1793
|
const ctx = {
|
|
1466
|
-
signal:
|
|
1794
|
+
signal: toolSignals.length === 1 ? toolTimeout : AbortSignal.any(toolSignals),
|
|
1467
1795
|
toolCallId: toolCall.id,
|
|
1468
1796
|
onUpdate: (update) => {
|
|
1469
1797
|
pushEvent({
|
|
@@ -1473,13 +1801,25 @@ async function executeSingleToolCall(toolCall, options, pushEvent) {
|
|
|
1473
1801
|
});
|
|
1474
1802
|
}
|
|
1475
1803
|
};
|
|
1804
|
+
const hardSignals = [callerSignal, toolTimeout].filter(
|
|
1805
|
+
(s) => s !== void 0
|
|
1806
|
+
);
|
|
1476
1807
|
const raw = await abortablePromise(
|
|
1477
1808
|
Promise.resolve().then(() => tool.execute(parsed, ctx)),
|
|
1478
|
-
|
|
1809
|
+
hardSignals.length === 1 ? toolTimeout : AbortSignal.any(hardSignals)
|
|
1479
1810
|
);
|
|
1480
1811
|
const normalized = normalizeToolResult(raw);
|
|
1481
|
-
resultContent = (0,
|
|
1482
|
-
details = (0,
|
|
1812
|
+
resultContent = (0, import_ai2.redactValue)(normalized.content, toolRedactionOptions());
|
|
1813
|
+
details = (0, import_ai2.redactValue)(normalized.details, toolRedactionOptions());
|
|
1814
|
+
if (options.transformToolResult) {
|
|
1815
|
+
try {
|
|
1816
|
+
resultContent = options.transformToolResult(
|
|
1817
|
+
{ name: toolCall.name, args: toolCall.args },
|
|
1818
|
+
resultContent
|
|
1819
|
+
);
|
|
1820
|
+
} catch {
|
|
1821
|
+
}
|
|
1822
|
+
}
|
|
1483
1823
|
for (const key of options.invalidToolArgumentCounts.keys()) {
|
|
1484
1824
|
if (key.startsWith(`${toolCall.name}:`)) options.invalidToolArgumentCounts.delete(key);
|
|
1485
1825
|
}
|
|
@@ -1491,12 +1831,14 @@ async function executeSingleToolCall(toolCall, options, pushEvent) {
|
|
|
1491
1831
|
const failureCount = (options.invalidToolArgumentCounts.get(failureKey) ?? 0) + 1;
|
|
1492
1832
|
options.invalidToolArgumentCounts.set(failureKey, failureCount);
|
|
1493
1833
|
invalidArgAttempt = failureCount;
|
|
1834
|
+
const hints = argumentHints(tool, toolCall.args);
|
|
1494
1835
|
resultContent = `Invalid arguments for tool \`${toolCall.name}\`:
|
|
1495
|
-
` + prettyError +
|
|
1836
|
+
` + prettyError + (hints ? `
|
|
1837
|
+
${hints}` : "") + "\nRe-issue the call with each field as the correct type.";
|
|
1496
1838
|
if (failureCount >= 3) {
|
|
1497
1839
|
const recoverable = Object.keys(toolCall.args ?? {}).length === 0;
|
|
1498
1840
|
options.markFatalToolArgumentError(
|
|
1499
|
-
new
|
|
1841
|
+
new import_ai2.EZCoderAIError(
|
|
1500
1842
|
`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
1843
|
{
|
|
1502
1844
|
source: "provider",
|
|
@@ -1510,15 +1852,24 @@ async function executeSingleToolCall(toolCall, options, pushEvent) {
|
|
|
1510
1852
|
} else if (options.signal?.aborted && isAbortError(err)) {
|
|
1511
1853
|
resultContent = indeterminateOutcomeText(toolCall.name);
|
|
1512
1854
|
} else {
|
|
1513
|
-
resultContent = (0,
|
|
1855
|
+
resultContent = (0, import_ai2.redactValue)(
|
|
1514
1856
|
err instanceof Error ? err.message : String(err),
|
|
1515
1857
|
toolRedactionOptions()
|
|
1516
1858
|
);
|
|
1517
1859
|
}
|
|
1518
1860
|
}
|
|
1519
1861
|
}
|
|
1520
|
-
resultContent = (0,
|
|
1521
|
-
details = (0,
|
|
1862
|
+
resultContent = (0, import_ai2.redactValue)(resultContent, toolRedactionOptions());
|
|
1863
|
+
details = (0, import_ai2.redactValue)(details, toolRedactionOptions());
|
|
1864
|
+
if (options.preemptSignal?.aborted && isInterruptibleTool(options.toolMap.get(toolCall.name), toolCall.name)) {
|
|
1865
|
+
const marked = markSteerInterrupted({
|
|
1866
|
+
toolCallId: toolCall.id,
|
|
1867
|
+
content: resultContent,
|
|
1868
|
+
isError
|
|
1869
|
+
});
|
|
1870
|
+
resultContent = marked.content;
|
|
1871
|
+
isError = true;
|
|
1872
|
+
}
|
|
1522
1873
|
const durationMs = Date.now() - startTime;
|
|
1523
1874
|
pushEvent({
|
|
1524
1875
|
type: "tool_call_end",
|
|
@@ -1531,13 +1882,67 @@ async function executeSingleToolCall(toolCall, options, pushEvent) {
|
|
|
1531
1882
|
});
|
|
1532
1883
|
return { toolCallId: toolCall.id, content: resultContent, isError };
|
|
1533
1884
|
}
|
|
1885
|
+
function createPreemptTracker(options, eventStream, state, resultsById, dispatchedIds) {
|
|
1886
|
+
const running = /* @__PURE__ */ new Map();
|
|
1887
|
+
let graceTimer;
|
|
1888
|
+
const preemptSignal = options.preemptSignal;
|
|
1889
|
+
const abandon = () => {
|
|
1890
|
+
graceTimer = void 0;
|
|
1891
|
+
for (const [id, entry] of running) {
|
|
1892
|
+
if (!entry.interruptible || resultsById.has(id)) continue;
|
|
1893
|
+
resultsById.set(id, { toolCallId: id, content: STEER_INTERRUPTED_TEXT, isError: true });
|
|
1894
|
+
pushToolEvent(eventStream, state, {
|
|
1895
|
+
type: "tool_call_end",
|
|
1896
|
+
toolCallId: id,
|
|
1897
|
+
result: STEER_INTERRUPTED_TEXT,
|
|
1898
|
+
isError: true,
|
|
1899
|
+
durationMs: Date.now() - entry.startedAt
|
|
1900
|
+
});
|
|
1901
|
+
running.delete(id);
|
|
1902
|
+
}
|
|
1903
|
+
if (running.size === 0) eventStream.close();
|
|
1904
|
+
};
|
|
1905
|
+
const onPreempt = () => {
|
|
1906
|
+
if (graceTimer === void 0) graceTimer = setTimeout(abandon, PREEMPT_GRACE_MS);
|
|
1907
|
+
};
|
|
1908
|
+
preemptSignal?.addEventListener("abort", onPreempt, { once: true });
|
|
1909
|
+
return {
|
|
1910
|
+
async run(toolCall) {
|
|
1911
|
+
if (options.preemptSignal?.aborted) return;
|
|
1912
|
+
dispatchedIds.add(toolCall.id);
|
|
1913
|
+
const interruptible = isInterruptibleTool(options.toolMap.get(toolCall.name), toolCall.name);
|
|
1914
|
+
running.set(toolCall.id, { toolCall, interruptible, startedAt: Date.now() });
|
|
1915
|
+
const record = await executeSingleToolCall(
|
|
1916
|
+
toolCall,
|
|
1917
|
+
options,
|
|
1918
|
+
(event) => pushToolEvent(eventStream, state, event)
|
|
1919
|
+
);
|
|
1920
|
+
running.delete(toolCall.id);
|
|
1921
|
+
if (resultsById.has(toolCall.id)) return;
|
|
1922
|
+
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);
|
|
1923
|
+
resultsById.set(
|
|
1924
|
+
toolCall.id,
|
|
1925
|
+
interruptible && preemptSignal?.aborted && !alreadyMarked ? markSteerInterrupted(record) : record
|
|
1926
|
+
);
|
|
1927
|
+
if (preemptSignal?.aborted && graceTimer === void 0 && running.size === 0) {
|
|
1928
|
+
eventStream.close();
|
|
1929
|
+
}
|
|
1930
|
+
},
|
|
1931
|
+
preempted: () => preemptSignal?.aborted === true,
|
|
1932
|
+
dispose: () => {
|
|
1933
|
+
if (graceTimer !== void 0) clearTimeout(graceTimer);
|
|
1934
|
+
preemptSignal?.removeEventListener("abort", onPreempt);
|
|
1935
|
+
}
|
|
1936
|
+
};
|
|
1937
|
+
}
|
|
1534
1938
|
async function* executeToolCallsMixed(toolCalls, initialToolResults, options) {
|
|
1535
|
-
const eventStream = new
|
|
1939
|
+
const eventStream = new import_ai2.EventStream();
|
|
1536
1940
|
const state = { finalized: false };
|
|
1537
1941
|
const resultsById = /* @__PURE__ */ new Map();
|
|
1538
1942
|
const dispatchedIds = /* @__PURE__ */ new Set();
|
|
1539
1943
|
const abortHandler = () => eventStream.abort(new Error("aborted"));
|
|
1540
1944
|
options.signal?.addEventListener("abort", abortHandler, { once: true });
|
|
1945
|
+
const tracker = createPreemptTracker(options, eventStream, state, resultsById, dispatchedIds);
|
|
1541
1946
|
const phases = [];
|
|
1542
1947
|
let currentParallel = [];
|
|
1543
1948
|
for (const toolCall of toolCalls) {
|
|
@@ -1558,40 +1963,18 @@ async function* executeToolCallsMixed(toolCalls, initialToolResults, options) {
|
|
|
1558
1963
|
void (async () => {
|
|
1559
1964
|
try {
|
|
1560
1965
|
for (const phase of phases) {
|
|
1561
|
-
if (options.signal?.aborted) break;
|
|
1966
|
+
if (options.signal?.aborted || options.preemptSignal?.aborted) break;
|
|
1562
1967
|
if (phase.sequential) {
|
|
1563
1968
|
const signature = JSON.stringify([
|
|
1564
1969
|
phase.sequential.name,
|
|
1565
1970
|
canonicalToolArgs(phase.sequential.args)
|
|
1566
1971
|
]);
|
|
1567
1972
|
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);
|
|
1973
|
+
await tracker.run(phase.sequential);
|
|
1575
1974
|
} 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);
|
|
1975
|
+
await tracker.run(phase.parallel[0]);
|
|
1583
1976
|
} 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
|
-
);
|
|
1977
|
+
await Promise.all(phase.parallel.map((toolCall) => tracker.run(toolCall)));
|
|
1595
1978
|
}
|
|
1596
1979
|
}
|
|
1597
1980
|
if (!state.finalized) eventStream.close();
|
|
@@ -1612,31 +1995,23 @@ async function* executeToolCallsMixed(toolCalls, initialToolResults, options) {
|
|
|
1612
1995
|
}
|
|
1613
1996
|
} finally {
|
|
1614
1997
|
options.signal?.removeEventListener("abort", abortHandler);
|
|
1998
|
+
tracker.dispose();
|
|
1615
1999
|
state.finalized = true;
|
|
1616
2000
|
}
|
|
1617
2001
|
const toolResults = buildToolResults(initialToolResults, toolCalls, resultsById, dispatchedIds);
|
|
1618
2002
|
capToolResults(toolResults, options.maxToolResultChars);
|
|
1619
2003
|
capTurnToolResults(toolResults, options.maxTurnToolResultChars);
|
|
1620
|
-
return { toolResults, aborted };
|
|
2004
|
+
return { toolResults, aborted, preempted: !aborted && tracker.preempted() };
|
|
1621
2005
|
}
|
|
1622
2006
|
async function* executeToolCallsParallel(toolCalls, initialToolResults, options) {
|
|
1623
|
-
const eventStream = new
|
|
2007
|
+
const eventStream = new import_ai2.EventStream();
|
|
1624
2008
|
const state = { finalized: false };
|
|
1625
2009
|
const resultsById = /* @__PURE__ */ new Map();
|
|
1626
2010
|
const dispatchedIds = /* @__PURE__ */ new Set();
|
|
1627
2011
|
const abortHandler = () => eventStream.abort(new Error("aborted"));
|
|
1628
2012
|
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(() => {
|
|
2013
|
+
const tracker = createPreemptTracker(options, eventStream, state, resultsById, dispatchedIds);
|
|
2014
|
+
Promise.all(toolCalls.map((toolCall) => tracker.run(toolCall))).then(() => {
|
|
1640
2015
|
if (!state.finalized) eventStream.close();
|
|
1641
2016
|
}).catch((err) => {
|
|
1642
2017
|
if (!state.finalized) eventStream.abort(err instanceof Error ? err : new Error(String(err)));
|
|
@@ -1654,12 +2029,13 @@ async function* executeToolCallsParallel(toolCalls, initialToolResults, options)
|
|
|
1654
2029
|
}
|
|
1655
2030
|
} finally {
|
|
1656
2031
|
options.signal?.removeEventListener("abort", abortHandler);
|
|
2032
|
+
tracker.dispose();
|
|
1657
2033
|
state.finalized = true;
|
|
1658
2034
|
}
|
|
1659
2035
|
const toolResults = buildToolResults(initialToolResults, toolCalls, resultsById, dispatchedIds);
|
|
1660
2036
|
capToolResults(toolResults, options.maxToolResultChars);
|
|
1661
2037
|
capTurnToolResults(toolResults, options.maxTurnToolResultChars);
|
|
1662
|
-
return { toolResults, aborted };
|
|
2038
|
+
return { toolResults, aborted, preempted: !aborted && tracker.preempted() };
|
|
1663
2039
|
}
|
|
1664
2040
|
function cancelledBeforeStartText(name) {
|
|
1665
2041
|
return `\`${name}\` was cancelled before it started, so it had no effect. Safe to retry.`;
|
|
@@ -1699,8 +2075,8 @@ function capToolResults(toolResults, maxToolResultChars) {
|
|
|
1699
2075
|
const originalChars = toolResult.content.length;
|
|
1700
2076
|
const headChars = Math.floor(max * 0.7);
|
|
1701
2077
|
const tailChars = max - headChars;
|
|
1702
|
-
const head = (0,
|
|
1703
|
-
const tail = (0,
|
|
2078
|
+
const head = (0, import_ai2.sliceHead)(toolResult.content, headChars);
|
|
2079
|
+
const tail = (0, import_ai2.sliceTail)(toolResult.content, tailChars);
|
|
1704
2080
|
const omitted = originalChars - head.length - tail.length;
|
|
1705
2081
|
toolResult.content = head + `
|
|
1706
2082
|
|
|
@@ -1736,11 +2112,11 @@ function capTurnToolResults(toolResults, maxTurnToolResultChars) {
|
|
|
1736
2112
|
const headChars = Math.floor(fairShare * 0.7);
|
|
1737
2113
|
const tailChars = fairShare - headChars;
|
|
1738
2114
|
const omitted = originalChars - fairShare;
|
|
1739
|
-
toolResult.content = (0,
|
|
2115
|
+
toolResult.content = (0, import_ai2.sliceHead)(toolResult.content, headChars) + `
|
|
1740
2116
|
|
|
1741
2117
|
[... ${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
2118
|
|
|
1743
|
-
` + (0,
|
|
2119
|
+
` + (0, import_ai2.sliceTail)(toolResult.content, tailChars);
|
|
1744
2120
|
toolResult.capped = {
|
|
1745
2121
|
originalChars: toolResult.capped?.originalChars ?? originalChars,
|
|
1746
2122
|
keptChars: toolResult.content.length,
|
|
@@ -1759,8 +2135,8 @@ function truncateToolResultText(text, maxChars) {
|
|
|
1759
2135
|
if (text.length <= maxChars) return text;
|
|
1760
2136
|
const tailChars = Math.min(Math.floor(maxChars * 0.3), 2e4);
|
|
1761
2137
|
const headChars = Math.max(maxChars - tailChars, 0);
|
|
1762
|
-
const head = (0,
|
|
1763
|
-
const tail = (0,
|
|
2138
|
+
const head = (0, import_ai2.sliceHead)(text, headChars);
|
|
2139
|
+
const tail = (0, import_ai2.sliceTail)(text, tailChars);
|
|
1764
2140
|
const omitted = text.length - head.length - tail.length;
|
|
1765
2141
|
return `${head}
|
|
1766
2142
|
|
|
@@ -1826,6 +2202,19 @@ function sanitizeOrphanedServerTools(messages) {
|
|
|
1826
2202
|
break;
|
|
1827
2203
|
}
|
|
1828
2204
|
}
|
|
2205
|
+
function countMessageChars(messages) {
|
|
2206
|
+
let chars = 0;
|
|
2207
|
+
for (const m of messages) {
|
|
2208
|
+
if (typeof m.content === "string") chars += m.content.length;
|
|
2209
|
+
else if (Array.isArray(m.content)) {
|
|
2210
|
+
for (const p of m.content) {
|
|
2211
|
+
if ("text" in p && typeof p.text === "string") chars += p.text.length;
|
|
2212
|
+
if ("content" in p && typeof p.content === "string") chars += p.content.length;
|
|
2213
|
+
}
|
|
2214
|
+
}
|
|
2215
|
+
}
|
|
2216
|
+
return chars;
|
|
2217
|
+
}
|
|
1829
2218
|
function repairToolPairingAdjacent(messages) {
|
|
1830
2219
|
for (let i = 0; i < messages.length; i++) {
|
|
1831
2220
|
const msg = messages[i];
|
|
@@ -1896,7 +2285,7 @@ var AgentStream = class {
|
|
|
1896
2285
|
rejectResult;
|
|
1897
2286
|
hasConsumer = false;
|
|
1898
2287
|
constructor(generator, onDone) {
|
|
1899
|
-
this.events = new
|
|
2288
|
+
this.events = new import_ai3.EventStream();
|
|
1900
2289
|
this.resultPromise = new Promise((resolve, reject) => {
|
|
1901
2290
|
this.resolveResult = resolve;
|
|
1902
2291
|
this.rejectResult = reject;
|
|
@@ -1942,6 +2331,7 @@ var Agent = class {
|
|
|
1942
2331
|
options;
|
|
1943
2332
|
steeringQueue = [];
|
|
1944
2333
|
followUpQueue = [];
|
|
2334
|
+
steeringListeners = /* @__PURE__ */ new Set();
|
|
1945
2335
|
constructor(options) {
|
|
1946
2336
|
this.options = options;
|
|
1947
2337
|
if (options.system) {
|
|
@@ -1966,9 +2356,14 @@ var Agent = class {
|
|
|
1966
2356
|
get running() {
|
|
1967
2357
|
return this._running;
|
|
1968
2358
|
}
|
|
1969
|
-
/**
|
|
2359
|
+
/**
|
|
2360
|
+
* Queue a steering message. Instant interrupt: if tools are running, the
|
|
2361
|
+
* interruptible ones are preempted right away and the message is injected
|
|
2362
|
+
* before the next model call (the turn continues — unlike abort/Stop).
|
|
2363
|
+
*/
|
|
1970
2364
|
steer(msg) {
|
|
1971
2365
|
this.steeringQueue.push(msg);
|
|
2366
|
+
for (const listener of [...this.steeringListeners]) listener();
|
|
1972
2367
|
}
|
|
1973
2368
|
/** Queue a follow-up message for injection when the agent would otherwise stop. */
|
|
1974
2369
|
followUp(msg) {
|
|
@@ -1988,6 +2383,14 @@ var Agent = class {
|
|
|
1988
2383
|
const all = [...callerResult ?? [], ...queued];
|
|
1989
2384
|
return all.length > 0 ? all : null;
|
|
1990
2385
|
},
|
|
2386
|
+
onSteeringAvailable: (listener) => {
|
|
2387
|
+
this.steeringListeners.add(listener);
|
|
2388
|
+
const unsubscribeCaller = this.options.onSteeringAvailable?.(listener);
|
|
2389
|
+
return () => {
|
|
2390
|
+
this.steeringListeners.delete(listener);
|
|
2391
|
+
unsubscribeCaller?.();
|
|
2392
|
+
};
|
|
2393
|
+
},
|
|
1991
2394
|
getFollowUpMessages: async () => {
|
|
1992
2395
|
const callerResult = await this.options.getFollowUpMessages?.() ?? [];
|
|
1993
2396
|
const queued = this.followUpQueue.splice(0);
|
|
@@ -2005,7 +2408,12 @@ var Agent = class {
|
|
|
2005
2408
|
0 && (module.exports = {
|
|
2006
2409
|
Agent,
|
|
2007
2410
|
AgentStream,
|
|
2411
|
+
DEFAULT_STREAM_RULE_MAX_RETRIES,
|
|
2412
|
+
DEFAULT_STREAM_RULE_WINDOW_CHARS,
|
|
2413
|
+
JsonEscapeDecoder,
|
|
2414
|
+
StreamRuleMonitor,
|
|
2008
2415
|
agentLoop,
|
|
2416
|
+
buildStreamRuleReminder,
|
|
2009
2417
|
cancelledBeforeStartText,
|
|
2010
2418
|
indeterminateOutcomeText,
|
|
2011
2419
|
isAbortError,
|
|
@@ -2013,6 +2421,7 @@ var Agent = class {
|
|
|
2013
2421
|
isContextOverflow,
|
|
2014
2422
|
isLocalBackendUrl,
|
|
2015
2423
|
isUsageLimitError,
|
|
2424
|
+
repairToolPairingAdjacent,
|
|
2016
2425
|
setStreamDiagnostic
|
|
2017
2426
|
});
|
|
2018
2427
|
//# sourceMappingURL=index.cjs.map
|