@prestyj/agent 5.8.0 → 5.9.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 CHANGED
@@ -215,7 +215,11 @@ function createAbortError() {
215
215
  }
216
216
  function abortablePromise(promise, signal) {
217
217
  if (!signal) return promise;
218
- if (signal.aborted) return Promise.reject(createAbortError());
218
+ if (signal.aborted) {
219
+ promise.catch(() => {
220
+ });
221
+ return Promise.reject(createAbortError());
222
+ }
219
223
  return new Promise((resolve, reject) => {
220
224
  let settled = false;
221
225
  const cleanup = () => signal.removeEventListener("abort", onAbort);
@@ -271,6 +275,7 @@ async function* agentLoop(messages, options) {
271
275
  let overloadRetries = 0;
272
276
  let emptyResponseRetries = 0;
273
277
  let stallRetries = 0;
278
+ let runawayToolcallRetries = 0;
274
279
  let overflowCompactionAttempts = 0;
275
280
  let toolResultTruncationAttempted = false;
276
281
  const invalidToolArgumentCounts = /* @__PURE__ */ new Map();
@@ -279,11 +284,16 @@ async function* agentLoop(messages, options) {
279
284
  const MAX_OVERLOAD_RETRIES = 10;
280
285
  const MAX_EMPTY_RESPONSE_RETRIES = 2;
281
286
  const MAX_STALL_RETRIES = 10;
287
+ const MAX_RUNAWAY_TOOLCALL_RETRIES = 2;
288
+ const RUNAWAY_TOOLCALL_RETRY_DELAY_MS = 1e3;
282
289
  const MAX_OVERFLOW_COMPACTIONS = 2;
283
290
  const STALL_RETRIES_BEFORE_NON_STREAMING = 2;
284
291
  const STALL_DELAY_MS = 1e3;
285
292
  const MIN_PARTIAL_PRESERVE_CHARS = 200;
286
293
  const PARTIAL_CONTINUATION_PROMPT = "[Your previous response was cut off by a connection failure. The text above is what was already delivered to the user. Continue exactly from where it stopped \u2014 do not repeat or restart it.]";
294
+ const MAX_OUTPUT_CONTINUATIONS = 2;
295
+ const MAX_TOKENS_CONTINUATION_PROMPT = "[Your previous response hit the output-token limit and was cut off. The text above is what was already delivered to the user. Continue exactly from where it stopped \u2014 do not repeat or restart it.]";
296
+ let maxTokensContinuations = 0;
287
297
  const OVERLOAD_BASE_DELAY_MS = 2e3;
288
298
  const OVERLOAD_MAX_DELAY_MS = 3e4;
289
299
  const STREAM_FIRST_EVENT_TIMEOUT_MS = 45e3;
@@ -686,11 +696,33 @@ async function* agentLoop(messages, options) {
686
696
  provider: options.provider,
687
697
  model: options.model
688
698
  });
699
+ if (runawayToolcallRetries < MAX_RUNAWAY_TOOLCALL_RETRIES) {
700
+ runawayToolcallRetries++;
701
+ const delayMs = RUNAWAY_TOOLCALL_RETRY_DELAY_MS * runawayToolcallRetries;
702
+ diag("retry", {
703
+ reason: "runaway_toolcall",
704
+ attempt: runawayToolcallRetries,
705
+ maxAttempts: MAX_RUNAWAY_TOOLCALL_RETRIES,
706
+ delayMs,
707
+ ...runawayDetected
708
+ });
709
+ yield {
710
+ type: "retry",
711
+ reason: "runaway_toolcall",
712
+ attempt: runawayToolcallRetries,
713
+ maxAttempts: MAX_RUNAWAY_TOOLCALL_RETRIES,
714
+ delayMs,
715
+ silent: true
716
+ };
717
+ await abortableSleep(delayMs, options.signal);
718
+ turn--;
719
+ continue;
720
+ }
689
721
  const detail = runawayDetected.kind === "chars" ? `${(runawayDetected.chars / 1024).toFixed(0)} KB of tool-call arguments` : `${runawayDetected.events} tool-call delta events`;
690
722
  yield {
691
723
  type: "error",
692
724
  error: new Error(
693
- `The model glitched mid-tool-call and produced ${detail} without closing the call. This is usually an upstream model bug \u2014 try the same request again or switch models. Your conversation is preserved.`
725
+ `The model repeatedly failed to close a tool call after ${MAX_RUNAWAY_TOOLCALL_RETRIES} automatic retries (${detail}). Switch models and retry; your conversation is preserved.`
694
726
  )
695
727
  };
696
728
  break;
@@ -791,6 +823,7 @@ async function* agentLoop(messages, options) {
791
823
  }
792
824
  overloadRetries = 0;
793
825
  stallRetries = 0;
826
+ runawayToolcallRetries = 0;
794
827
  const contentArr = Array.isArray(response.message.content) ? response.message.content : null;
795
828
  const hasActionableContent = response.message.content !== "" && contentArr !== null && contentArr.some(
796
829
  (p) => p.type === "text" || p.type === "tool_call" || p.type === "server_tool_call"
@@ -862,6 +895,27 @@ async function* agentLoop(messages, options) {
862
895
  consecutivePauses = 0;
863
896
  const allToolCalls = extractToolCalls(response.message.content);
864
897
  if (response.stopReason !== "tool_use" && allToolCalls.length === 0) {
898
+ if (response.stopReason === "max_tokens") {
899
+ if (maxTokensContinuations < MAX_OUTPUT_CONTINUATIONS) {
900
+ maxTokensContinuations++;
901
+ diag("max_tokens_continuation", {
902
+ attempt: maxTokensContinuations,
903
+ maxAttempts: MAX_OUTPUT_CONTINUATIONS,
904
+ provider: options.provider,
905
+ model: options.model
906
+ });
907
+ yield { type: "truncated", reason: "max_tokens", continued: true };
908
+ messages.push({ role: "user", content: MAX_TOKENS_CONTINUATION_PROMPT });
909
+ continue;
910
+ }
911
+ yield { type: "truncated", reason: "max_tokens", continued: false };
912
+ } else if (response.stopReason === "refusal" || response.stopReason === "error") {
913
+ yield {
914
+ type: "truncated",
915
+ reason: response.stopReason === "refusal" ? "refusal" : "provider_error",
916
+ continued: false
917
+ };
918
+ }
865
919
  if (options.getSteeringMessages) {
866
920
  const steering = await options.getSteeringMessages();
867
921
  if (steering && steering.length > 0) {
@@ -1228,16 +1282,22 @@ function capToolResults(toolResults, maxToolResultChars) {
1228
1282
  const max = Math.min(maxToolResultChars, hardMax);
1229
1283
  for (const toolResult of toolResults) {
1230
1284
  if (typeof toolResult.content !== "string" || toolResult.content.length <= max) continue;
1285
+ const originalChars = toolResult.content.length;
1231
1286
  const headChars = Math.floor(max * 0.7);
1232
1287
  const tailChars = max - headChars;
1233
1288
  const head = toolResult.content.slice(0, headChars);
1234
1289
  const tail = toolResult.content.slice(-tailChars);
1235
- const omitted = toolResult.content.length - headChars - tailChars;
1290
+ const omitted = originalChars - headChars - tailChars;
1236
1291
  toolResult.content = head + `
1237
1292
 
1238
1293
  [... ${omitted} characters omitted ...]
1239
1294
 
1240
1295
  ` + tail;
1296
+ toolResult.capped = {
1297
+ originalChars,
1298
+ keptChars: toolResult.content.length,
1299
+ scope: "per-result"
1300
+ };
1241
1301
  }
1242
1302
  }
1243
1303
  function capTurnToolResults(toolResults, maxTurnToolResultChars) {
@@ -1258,14 +1318,20 @@ function capTurnToolResults(toolResults, maxTurnToolResultChars) {
1258
1318
  continue;
1259
1319
  }
1260
1320
  remaining -= fairShare;
1321
+ const originalChars = toolResult.content.length;
1261
1322
  const headChars = Math.floor(fairShare * 0.7);
1262
1323
  const tailChars = fairShare - headChars;
1263
- const omitted = toolResult.content.length - fairShare;
1324
+ const omitted = originalChars - fairShare;
1264
1325
  toolResult.content = toolResult.content.slice(0, headChars) + `
1265
1326
 
1266
1327
  [... ${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 ...]
1267
1328
 
1268
1329
  ` + (tailChars > 0 ? toolResult.content.slice(-tailChars) : "");
1330
+ toolResult.capped = {
1331
+ originalChars: toolResult.capped?.originalChars ?? originalChars,
1332
+ keptChars: toolResult.content.length,
1333
+ scope: "per-turn"
1334
+ };
1269
1335
  }
1270
1336
  }
1271
1337
  function normalizeToolResult(raw) {