@prestyj/agent 5.27.0 → 5.28.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 CHANGED
@@ -460,11 +460,14 @@ async function* agentLoop(messages, options) {
460
460
  const OVERLOAD_BASE_DELAY_MS = 2e3;
461
461
  const OVERLOAD_MAX_DELAY_MS = 3e4;
462
462
  const STREAM_IDLE_TIMEOUT_MS = 9e4;
463
+ const STREAM_TOOL_INPUT_IDLE_TIMEOUT_MS = 3e5;
463
464
  const STREAM_HARD_TIMEOUT_MS = 9e4;
464
465
  const STREAM_OUTPUT_HARD_TIMEOUT_MS = 3e5;
465
- const STREAM_THINKING_IDLE_TIMEOUT_MS = 3e5;
466
- const STREAM_THINKING_HARD_TIMEOUT_MS = 6e5;
466
+ const STREAM_THINKING_IDLE_TIMEOUT_MS = 6e5;
467
+ const STREAM_THINKING_HARD_TIMEOUT_MS = 9e5;
467
468
  const NON_STREAMING_HARD_TIMEOUT_MS = 3e5;
469
+ const nonStreamingHardTimeoutMs = options.thinking != null ? STREAM_THINKING_HARD_TIMEOUT_MS : NON_STREAMING_HARD_TIMEOUT_MS;
470
+ const VISIBLE_STALL_AFTER_MS = 6e4;
468
471
  const usesSilentReasoningBudget = options.provider === "sakana" || options.provider === "openai" && options.thinking != null;
469
472
  const localBackend = isLocalBackendUrl(options.baseUrl);
470
473
  const baseFirstEventTimeoutMs = localBackend ? Number.POSITIVE_INFINITY : usesSilentReasoningBudget ? STREAM_THINKING_IDLE_TIMEOUT_MS : STREAM_FIRST_EVENT_TIMEOUT_MS;
@@ -566,10 +569,11 @@ async function* agentLoop(messages, options) {
566
569
  options.signal?.addEventListener("abort", forwardAbort, { once: true });
567
570
  let hasReceivedEvent = false;
568
571
  let hasReceivedThinking = false;
572
+ let toolInputOpen = false;
569
573
  const resetIdleTimer = () => {
570
574
  if (useNonStreamingFallback) return;
571
575
  if (idleTimer) clearTimeout(idleTimer);
572
- const timeoutMs = hasReceivedEvent ? STREAM_IDLE_TIMEOUT_MS : hasReceivedThinking ? STREAM_THINKING_IDLE_TIMEOUT_MS : firstEventTimeoutMs;
576
+ const timeoutMs = hasReceivedEvent ? toolInputOpen ? STREAM_TOOL_INPUT_IDLE_TIMEOUT_MS : STREAM_IDLE_TIMEOUT_MS : hasReceivedThinking ? STREAM_THINKING_IDLE_TIMEOUT_MS : firstEventTimeoutMs;
573
577
  if (!Number.isFinite(timeoutMs)) return;
574
578
  idleTimer = setTimeout(() => {
575
579
  diag("idle_timeout_fired", {
@@ -577,14 +581,14 @@ async function* agentLoop(messages, options) {
577
581
  sinceLastEventMs: Date.now() - lastEventTime,
578
582
  lastEventType,
579
583
  maxConsumerLagMs,
580
- phase: hasReceivedEvent ? "mid_stream" : hasReceivedThinking ? "post_thinking" : "first_event",
584
+ phase: hasReceivedEvent ? toolInputOpen ? "mid_tool_input" : "mid_stream" : hasReceivedThinking ? "post_thinking" : "first_event",
581
585
  eventTypes: eventTypeCounts
582
586
  });
583
587
  idleTimedOut = true;
584
588
  streamController.abort();
585
589
  }, timeoutMs);
586
590
  };
587
- let hardTimeoutMs = useNonStreamingFallback ? NON_STREAMING_HARD_TIMEOUT_MS : initialHardTimeoutMs;
591
+ let hardTimeoutMs = useNonStreamingFallback ? nonStreamingHardTimeoutMs : initialHardTimeoutMs;
588
592
  hardTimer = setTimeout(() => {
589
593
  diag("hard_timeout_fired", {
590
594
  events: typeof streamEventCount !== "undefined" ? streamEventCount : 0,
@@ -679,6 +683,8 @@ async function* agentLoop(messages, options) {
679
683
  if (firstProviderEventAt === void 0) firstProviderEventAt = pullTime;
680
684
  eventTypeCounts[event.type] = (eventTypeCounts[event.type] ?? 0) + 1;
681
685
  lastEventType = event.type;
686
+ if (event.type === "toolcall_delta") toolInputOpen = true;
687
+ else if (event.type === "toolcall_done") toolInputOpen = false;
682
688
  if ((event.type === "text_delta" || event.type === "server_toolcall" || event.type === "toolcall_delta") && !hasReceivedEvent) {
683
689
  hasReceivedEvent = true;
684
690
  if (hardTimer && hardTimeoutMs < STREAM_OUTPUT_HARD_TIMEOUT_MS) {
@@ -1013,7 +1019,7 @@ async function* agentLoop(messages, options) {
1013
1019
  attempt: stallRetries,
1014
1020
  maxAttempts: MAX_STALL_RETRIES,
1015
1021
  delayMs,
1016
- silent: stallRetries <= 2,
1022
+ silent: stallRetries <= 2 && Date.now() - streamCallStart < VISIBLE_STALL_AFTER_MS,
1017
1023
  ...preservedChars > 0 ? { preservedChars } : {}
1018
1024
  };
1019
1025
  await abortableSleep(delayMs, options.signal);
@@ -1276,7 +1282,8 @@ async function* agentLoop(messages, options) {
1276
1282
  maxTurnToolResultChars: options.maxTurnToolResultChars,
1277
1283
  toolMap,
1278
1284
  invalidToolArgumentCounts,
1279
- markFatalToolArgumentError
1285
+ markFatalToolArgumentError,
1286
+ seenToolCalls: /* @__PURE__ */ new Set()
1280
1287
  };
1281
1288
  const hasSequentialToolCall = toolCalls.some(
1282
1289
  (toolCall) => toolMap.get(toolCall.name)?.executionMode === "sequential"
@@ -1397,6 +1404,15 @@ async function* agentLoop(messages, options) {
1397
1404
  totalUsage: { ...totalUsage }
1398
1405
  };
1399
1406
  }
1407
+ function canonicalToolArgs(value) {
1408
+ if (Array.isArray(value)) return value.map(canonicalToolArgs);
1409
+ if (value !== null && typeof value === "object") {
1410
+ return Object.fromEntries(
1411
+ Object.entries(value).sort(([a], [b]) => a.localeCompare(b)).map(([k, v]) => [k, canonicalToolArgs(v)])
1412
+ );
1413
+ }
1414
+ return value;
1415
+ }
1400
1416
  function pushToolEvent(eventStream, state, event) {
1401
1417
  if (!state.finalized) eventStream.push(event);
1402
1418
  }
@@ -1413,6 +1429,21 @@ async function executeSingleToolCall(toolCall, options, pushEvent) {
1413
1429
  let isError = false;
1414
1430
  let invalidArgAttempt;
1415
1431
  const tool = options.toolMap.get(toolCall.name);
1432
+ if (tool) {
1433
+ const signature = JSON.stringify([toolCall.name, canonicalToolArgs(toolCall.args)]);
1434
+ if (options.seenToolCalls.has(signature)) {
1435
+ const content = "Tool call cancelled: an identical call already appeared in this response; this call was not executed.";
1436
+ pushEvent({
1437
+ type: "tool_call_end",
1438
+ toolCallId: toolCall.id,
1439
+ result: content,
1440
+ isError: true,
1441
+ durationMs: Date.now() - startTime
1442
+ });
1443
+ return { toolCallId: toolCall.id, content, isError: true };
1444
+ }
1445
+ options.seenToolCalls.add(signature);
1446
+ }
1416
1447
  if (!tool) {
1417
1448
  resultContent = `Unknown tool: ${toolCall.name}`;
1418
1449
  isError = true;
@@ -1529,6 +1560,11 @@ async function* executeToolCallsMixed(toolCalls, initialToolResults, options) {
1529
1560
  for (const phase of phases) {
1530
1561
  if (options.signal?.aborted) break;
1531
1562
  if (phase.sequential) {
1563
+ const signature = JSON.stringify([
1564
+ phase.sequential.name,
1565
+ canonicalToolArgs(phase.sequential.args)
1566
+ ]);
1567
+ if (!options.seenToolCalls.has(signature)) options.seenToolCalls.clear();
1532
1568
  dispatchedIds.add(phase.sequential.id);
1533
1569
  const record = await executeSingleToolCall(
1534
1570
  phase.sequential,