@prestyj/agent 5.10.0 → 5.11.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 +102 -22
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +56 -1
- package/dist/index.d.ts +56 -1
- package/dist/index.js +105 -23
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.cjs
CHANGED
|
@@ -63,6 +63,7 @@ function isLocalBackendUrl(baseUrl) {
|
|
|
63
63
|
|
|
64
64
|
// src/agent-loop.ts
|
|
65
65
|
var DEFAULT_MAX_TURNS = 300;
|
|
66
|
+
var DEFAULT_TOOL_TIMEOUT_MS = 3e5;
|
|
66
67
|
var _diagFn = null;
|
|
67
68
|
function setStreamDiagnostic(fn) {
|
|
68
69
|
_diagFn = fn;
|
|
@@ -265,6 +266,9 @@ function abortablePromise(promise, signal) {
|
|
|
265
266
|
promise.then(resolveOnce, rejectOnce);
|
|
266
267
|
});
|
|
267
268
|
}
|
|
269
|
+
function turnBudgetContinuationPrompt() {
|
|
270
|
+
return "[You reached the turn limit for this segment but the work is not finished, so you have been granted more turns. Before continuing, state in one or two sentences what is already done and what remains, then keep going from there \u2014 do not restart work that is already complete.]";
|
|
271
|
+
}
|
|
268
272
|
function abortableSleep(ms, signal) {
|
|
269
273
|
if (signal?.aborted) return Promise.reject(createAbortError());
|
|
270
274
|
return new Promise((resolve, reject) => {
|
|
@@ -286,6 +290,9 @@ function closeIterator(iterator) {
|
|
|
286
290
|
}
|
|
287
291
|
async function* agentLoop(messages, options) {
|
|
288
292
|
const maxTurns = options.maxTurns ?? DEFAULT_MAX_TURNS;
|
|
293
|
+
let effectiveMaxTurns = maxTurns;
|
|
294
|
+
const maxTurnExtensions = Math.max(0, options.maxTurnExtensions ?? 2);
|
|
295
|
+
let turnExtensions = 0;
|
|
289
296
|
const maxContinuations = options.maxContinuations ?? 5;
|
|
290
297
|
let toolMap = new Map((options.tools ?? []).map((t) => [t.name, t]));
|
|
291
298
|
const totalUsage = { inputTokens: 0, outputTokens: 0 };
|
|
@@ -333,12 +340,12 @@ async function* agentLoop(messages, options) {
|
|
|
333
340
|
const firstEventTimeoutMs = localBackend ? Number.POSITIVE_INFINITY : usesSilentReasoningBudget ? STREAM_THINKING_IDLE_TIMEOUT_MS : STREAM_FIRST_EVENT_TIMEOUT_MS;
|
|
334
341
|
const initialHardTimeoutMs = localBackend || usesSilentReasoningBudget ? STREAM_THINKING_HARD_TIMEOUT_MS : STREAM_HARD_TIMEOUT_MS;
|
|
335
342
|
const MAX_TOOLCALL_DELTA_CHARS = 1e6;
|
|
336
|
-
const
|
|
343
|
+
const MAX_TOOLCALL_NO_PROGRESS_EVENTS = 2e4;
|
|
337
344
|
let logicalTurnStartedAt = 0;
|
|
338
345
|
let firstProviderEventAt;
|
|
339
346
|
let providerDurationMs = 0;
|
|
340
347
|
try {
|
|
341
|
-
while (turn <
|
|
348
|
+
while (turn < effectiveMaxTurns) {
|
|
342
349
|
options.signal?.throwIfAborted();
|
|
343
350
|
turn++;
|
|
344
351
|
if (logicalTurnStartedAt === 0) logicalTurnStartedAt = Date.now();
|
|
@@ -409,6 +416,7 @@ async function* agentLoop(messages, options) {
|
|
|
409
416
|
let lastEventType = "";
|
|
410
417
|
let toolcallDeltaChars = 0;
|
|
411
418
|
let toolcallDeltaCount = 0;
|
|
419
|
+
let toolcallNoProgressCount = 0;
|
|
412
420
|
let runawayDetected = null;
|
|
413
421
|
let attemptText = "";
|
|
414
422
|
let lastYieldEndTime = Date.now();
|
|
@@ -563,11 +571,13 @@ async function* agentLoop(messages, options) {
|
|
|
563
571
|
const chunkChars = event.argsJson?.length ?? 0;
|
|
564
572
|
toolcallDeltaChars += chunkChars;
|
|
565
573
|
toolcallDeltaCount++;
|
|
566
|
-
|
|
574
|
+
toolcallNoProgressCount = chunkChars > 0 ? 0 : toolcallNoProgressCount + 1;
|
|
575
|
+
if (!runawayDetected && (toolcallDeltaChars > MAX_TOOLCALL_DELTA_CHARS || toolcallNoProgressCount > MAX_TOOLCALL_NO_PROGRESS_EVENTS)) {
|
|
567
576
|
runawayDetected = {
|
|
568
577
|
kind: toolcallDeltaChars > MAX_TOOLCALL_DELTA_CHARS ? "chars" : "events",
|
|
569
578
|
chars: toolcallDeltaChars,
|
|
570
|
-
events: toolcallDeltaCount
|
|
579
|
+
events: toolcallDeltaCount,
|
|
580
|
+
noProgressEvents: toolcallNoProgressCount
|
|
571
581
|
};
|
|
572
582
|
diag("runaway_toolcall_detected", {
|
|
573
583
|
...runawayDetected,
|
|
@@ -749,11 +759,15 @@ async function* agentLoop(messages, options) {
|
|
|
749
759
|
turn--;
|
|
750
760
|
continue;
|
|
751
761
|
}
|
|
752
|
-
const detail = runawayDetected.kind === "chars" ? `${(runawayDetected.chars / 1024).toFixed(0)} KB of tool-call arguments` : `${runawayDetected.
|
|
762
|
+
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`;
|
|
753
763
|
yield {
|
|
754
764
|
type: "error",
|
|
755
|
-
error: new
|
|
756
|
-
`The model repeatedly failed to close a tool call after ${MAX_RUNAWAY_TOOLCALL_RETRIES} automatic retries (${detail}).
|
|
765
|
+
error: new import_ai.EZCoderAIError(
|
|
766
|
+
`The model repeatedly failed to close a tool call after ${MAX_RUNAWAY_TOOLCALL_RETRIES} automatic retries (${detail}). Your conversation is preserved.`,
|
|
767
|
+
{
|
|
768
|
+
source: "provider",
|
|
769
|
+
hint: "Retry once. If it keeps happening, switch models and continue the preserved conversation."
|
|
770
|
+
}
|
|
757
771
|
)
|
|
758
772
|
};
|
|
759
773
|
break;
|
|
@@ -780,7 +794,15 @@ async function* agentLoop(messages, options) {
|
|
|
780
794
|
role: "assistant",
|
|
781
795
|
content: [{ type: "text", text: attemptText }]
|
|
782
796
|
});
|
|
783
|
-
messages.push({
|
|
797
|
+
messages.push({
|
|
798
|
+
role: "user",
|
|
799
|
+
content: PARTIAL_CONTINUATION_PROMPT,
|
|
800
|
+
provenance: {
|
|
801
|
+
source: "runtime",
|
|
802
|
+
kind: "continuation",
|
|
803
|
+
visibility: "hidden"
|
|
804
|
+
}
|
|
805
|
+
});
|
|
784
806
|
preservedChars = attemptText.length;
|
|
785
807
|
}
|
|
786
808
|
diag("retry", {
|
|
@@ -899,6 +921,9 @@ async function* agentLoop(messages, options) {
|
|
|
899
921
|
useNonStreamingFallback = false;
|
|
900
922
|
totalUsage.inputTokens += response.usage.inputTokens;
|
|
901
923
|
totalUsage.outputTokens += response.usage.outputTokens;
|
|
924
|
+
if (response.usage.reasoningTokens) {
|
|
925
|
+
totalUsage.reasoningTokens = (totalUsage.reasoningTokens ?? 0) + response.usage.reasoningTokens;
|
|
926
|
+
}
|
|
902
927
|
if (response.usage.cacheRead) {
|
|
903
928
|
totalUsage.cacheRead = (totalUsage.cacheRead ?? 0) + response.usage.cacheRead;
|
|
904
929
|
}
|
|
@@ -950,7 +975,15 @@ async function* agentLoop(messages, options) {
|
|
|
950
975
|
model: options.model
|
|
951
976
|
});
|
|
952
977
|
yield { type: "truncated", reason: "max_tokens", continued: true };
|
|
953
|
-
messages.push({
|
|
978
|
+
messages.push({
|
|
979
|
+
role: "user",
|
|
980
|
+
content: MAX_TOKENS_CONTINUATION_PROMPT,
|
|
981
|
+
provenance: {
|
|
982
|
+
source: "runtime",
|
|
983
|
+
kind: "continuation",
|
|
984
|
+
visibility: "hidden"
|
|
985
|
+
}
|
|
986
|
+
});
|
|
954
987
|
continue;
|
|
955
988
|
}
|
|
956
989
|
yield { type: "truncated", reason: "max_tokens", continued: false };
|
|
@@ -1026,6 +1059,7 @@ async function* agentLoop(messages, options) {
|
|
|
1026
1059
|
);
|
|
1027
1060
|
const executionResult = hasSequentialToolCall ? yield* executeToolCallsMixed(toolCalls, toolResults, executionOptions) : yield* executeToolCallsParallel(toolCalls, toolResults, executionOptions);
|
|
1028
1061
|
messages.push({ role: "tool", content: executionResult.toolResults });
|
|
1062
|
+
yield { type: "checkpoint", turn };
|
|
1029
1063
|
const toolsAborted = executionResult.aborted;
|
|
1030
1064
|
if (fatalToolArgumentError) {
|
|
1031
1065
|
if (fatalToolArgumentRecoverable && !toolArgumentAutoContinueUsed) {
|
|
@@ -1057,8 +1091,51 @@ async function* agentLoop(messages, options) {
|
|
|
1057
1091
|
}
|
|
1058
1092
|
}
|
|
1059
1093
|
}
|
|
1060
|
-
if (turn >=
|
|
1061
|
-
|
|
1094
|
+
if (turn >= effectiveMaxTurns) {
|
|
1095
|
+
let extended = false;
|
|
1096
|
+
if (options.onTurnBudgetExhausted && turnExtensions < maxTurnExtensions) {
|
|
1097
|
+
const extension = turnExtensions + 1;
|
|
1098
|
+
let granted = false;
|
|
1099
|
+
try {
|
|
1100
|
+
granted = await options.onTurnBudgetExhausted({
|
|
1101
|
+
turn,
|
|
1102
|
+
maxTurns: effectiveMaxTurns,
|
|
1103
|
+
extension
|
|
1104
|
+
});
|
|
1105
|
+
} catch {
|
|
1106
|
+
granted = false;
|
|
1107
|
+
}
|
|
1108
|
+
if (granted) {
|
|
1109
|
+
turnExtensions = extension;
|
|
1110
|
+
effectiveMaxTurns += maxTurns;
|
|
1111
|
+
extended = true;
|
|
1112
|
+
diag("turn_budget_extended", {
|
|
1113
|
+
turn,
|
|
1114
|
+
grantedTurns: effectiveMaxTurns,
|
|
1115
|
+
extension,
|
|
1116
|
+
provider: options.provider,
|
|
1117
|
+
model: options.model
|
|
1118
|
+
});
|
|
1119
|
+
yield {
|
|
1120
|
+
type: "turn_budget_extended",
|
|
1121
|
+
turn,
|
|
1122
|
+
grantedTurns: effectiveMaxTurns,
|
|
1123
|
+
extension
|
|
1124
|
+
};
|
|
1125
|
+
messages.push({
|
|
1126
|
+
role: "user",
|
|
1127
|
+
content: turnBudgetContinuationPrompt(),
|
|
1128
|
+
provenance: {
|
|
1129
|
+
source: "runtime",
|
|
1130
|
+
kind: "continuation",
|
|
1131
|
+
visibility: "hidden"
|
|
1132
|
+
}
|
|
1133
|
+
});
|
|
1134
|
+
}
|
|
1135
|
+
}
|
|
1136
|
+
if (!extended) {
|
|
1137
|
+
hitMaxTurns = true;
|
|
1138
|
+
}
|
|
1062
1139
|
}
|
|
1063
1140
|
}
|
|
1064
1141
|
} finally {
|
|
@@ -1074,14 +1151,15 @@ async function* agentLoop(messages, options) {
|
|
|
1074
1151
|
if (hitMaxTurns) {
|
|
1075
1152
|
diag("max_turns_reached", {
|
|
1076
1153
|
turn,
|
|
1077
|
-
maxTurns,
|
|
1154
|
+
maxTurns: effectiveMaxTurns,
|
|
1155
|
+
extensions: turnExtensions,
|
|
1078
1156
|
provider: options.provider,
|
|
1079
1157
|
model: options.model
|
|
1080
1158
|
});
|
|
1081
1159
|
yield {
|
|
1082
1160
|
type: "max_turns",
|
|
1083
1161
|
totalTurns: turn,
|
|
1084
|
-
maxTurns
|
|
1162
|
+
maxTurns: effectiveMaxTurns
|
|
1085
1163
|
};
|
|
1086
1164
|
}
|
|
1087
1165
|
yield {
|
|
@@ -1117,7 +1195,7 @@ async function executeSingleToolCall(toolCall, options, pushEvent) {
|
|
|
1117
1195
|
try {
|
|
1118
1196
|
const parsed = tool.parameters.parse(toolCall.args);
|
|
1119
1197
|
const callerSignal = options.signal;
|
|
1120
|
-
const toolTimeout = AbortSignal.timeout(
|
|
1198
|
+
const toolTimeout = AbortSignal.timeout(tool.timeoutMs ?? DEFAULT_TOOL_TIMEOUT_MS);
|
|
1121
1199
|
const ctx = {
|
|
1122
1200
|
signal: callerSignal ? AbortSignal.any([callerSignal, toolTimeout]) : toolTimeout,
|
|
1123
1201
|
toolCallId: toolCall.id,
|
|
@@ -1330,9 +1408,9 @@ function capToolResults(toolResults, maxToolResultChars) {
|
|
|
1330
1408
|
const originalChars = toolResult.content.length;
|
|
1331
1409
|
const headChars = Math.floor(max * 0.7);
|
|
1332
1410
|
const tailChars = max - headChars;
|
|
1333
|
-
const head = toolResult.content
|
|
1334
|
-
const tail = toolResult.content
|
|
1335
|
-
const omitted = originalChars -
|
|
1411
|
+
const head = (0, import_ai.sliceHead)(toolResult.content, headChars);
|
|
1412
|
+
const tail = (0, import_ai.sliceTail)(toolResult.content, tailChars);
|
|
1413
|
+
const omitted = originalChars - head.length - tail.length;
|
|
1336
1414
|
toolResult.content = head + `
|
|
1337
1415
|
|
|
1338
1416
|
[... ${omitted} characters omitted ...]
|
|
@@ -1367,11 +1445,11 @@ function capTurnToolResults(toolResults, maxTurnToolResultChars) {
|
|
|
1367
1445
|
const headChars = Math.floor(fairShare * 0.7);
|
|
1368
1446
|
const tailChars = fairShare - headChars;
|
|
1369
1447
|
const omitted = originalChars - fairShare;
|
|
1370
|
-
toolResult.content = toolResult.content
|
|
1448
|
+
toolResult.content = (0, import_ai.sliceHead)(toolResult.content, headChars) + `
|
|
1371
1449
|
|
|
1372
1450
|
[... ${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 ...]
|
|
1373
1451
|
|
|
1374
|
-
` + (
|
|
1452
|
+
` + (0, import_ai.sliceTail)(toolResult.content, tailChars);
|
|
1375
1453
|
toolResult.capped = {
|
|
1376
1454
|
originalChars: toolResult.capped?.originalChars ?? originalChars,
|
|
1377
1455
|
keptChars: toolResult.content.length,
|
|
@@ -1390,12 +1468,14 @@ function truncateToolResultText(text, maxChars) {
|
|
|
1390
1468
|
if (text.length <= maxChars) return text;
|
|
1391
1469
|
const tailChars = Math.min(Math.floor(maxChars * 0.3), 2e4);
|
|
1392
1470
|
const headChars = Math.max(maxChars - tailChars, 0);
|
|
1393
|
-
const
|
|
1394
|
-
|
|
1471
|
+
const head = (0, import_ai.sliceHead)(text, headChars);
|
|
1472
|
+
const tail = (0, import_ai.sliceTail)(text, tailChars);
|
|
1473
|
+
const omitted = text.length - head.length - tail.length;
|
|
1474
|
+
return `${head}
|
|
1395
1475
|
|
|
1396
1476
|
[... ${omitted} characters omitted after context overflow ...]
|
|
1397
1477
|
|
|
1398
|
-
${
|
|
1478
|
+
${tail}`;
|
|
1399
1479
|
}
|
|
1400
1480
|
function truncateOversizedToolResults(messages, maxChars) {
|
|
1401
1481
|
if (maxChars <= 0) return false;
|