@prestyj/agent 5.26.0 → 5.28.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
@@ -120,6 +120,11 @@ function clampOutputTokens(key, requested) {
120
120
  // src/agent-loop.ts
121
121
  var DEFAULT_MAX_TURNS = 300;
122
122
  var DEFAULT_TOOL_TIMEOUT_MS = 3e5;
123
+ var _toolRedaction;
124
+ function toolRedactionOptions() {
125
+ _toolRedaction ??= { secrets: (0, import_ai.environmentSecrets)(process.env) };
126
+ return _toolRedaction;
127
+ }
123
128
  var _diagFn = null;
124
129
  function setStreamDiagnostic(fn) {
125
130
  _diagFn = fn;
@@ -455,11 +460,14 @@ async function* agentLoop(messages, options) {
455
460
  const OVERLOAD_BASE_DELAY_MS = 2e3;
456
461
  const OVERLOAD_MAX_DELAY_MS = 3e4;
457
462
  const STREAM_IDLE_TIMEOUT_MS = 9e4;
463
+ const STREAM_TOOL_INPUT_IDLE_TIMEOUT_MS = 3e5;
458
464
  const STREAM_HARD_TIMEOUT_MS = 9e4;
459
465
  const STREAM_OUTPUT_HARD_TIMEOUT_MS = 3e5;
460
- const STREAM_THINKING_IDLE_TIMEOUT_MS = 3e5;
461
- const STREAM_THINKING_HARD_TIMEOUT_MS = 6e5;
466
+ const STREAM_THINKING_IDLE_TIMEOUT_MS = 6e5;
467
+ const STREAM_THINKING_HARD_TIMEOUT_MS = 9e5;
462
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;
463
471
  const usesSilentReasoningBudget = options.provider === "sakana" || options.provider === "openai" && options.thinking != null;
464
472
  const localBackend = isLocalBackendUrl(options.baseUrl);
465
473
  const baseFirstEventTimeoutMs = localBackend ? Number.POSITIVE_INFINITY : usesSilentReasoningBudget ? STREAM_THINKING_IDLE_TIMEOUT_MS : STREAM_FIRST_EVENT_TIMEOUT_MS;
@@ -561,10 +569,11 @@ async function* agentLoop(messages, options) {
561
569
  options.signal?.addEventListener("abort", forwardAbort, { once: true });
562
570
  let hasReceivedEvent = false;
563
571
  let hasReceivedThinking = false;
572
+ let toolInputOpen = false;
564
573
  const resetIdleTimer = () => {
565
574
  if (useNonStreamingFallback) return;
566
575
  if (idleTimer) clearTimeout(idleTimer);
567
- 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;
568
577
  if (!Number.isFinite(timeoutMs)) return;
569
578
  idleTimer = setTimeout(() => {
570
579
  diag("idle_timeout_fired", {
@@ -572,14 +581,14 @@ async function* agentLoop(messages, options) {
572
581
  sinceLastEventMs: Date.now() - lastEventTime,
573
582
  lastEventType,
574
583
  maxConsumerLagMs,
575
- phase: hasReceivedEvent ? "mid_stream" : hasReceivedThinking ? "post_thinking" : "first_event",
584
+ phase: hasReceivedEvent ? toolInputOpen ? "mid_tool_input" : "mid_stream" : hasReceivedThinking ? "post_thinking" : "first_event",
576
585
  eventTypes: eventTypeCounts
577
586
  });
578
587
  idleTimedOut = true;
579
588
  streamController.abort();
580
589
  }, timeoutMs);
581
590
  };
582
- let hardTimeoutMs = useNonStreamingFallback ? NON_STREAMING_HARD_TIMEOUT_MS : initialHardTimeoutMs;
591
+ let hardTimeoutMs = useNonStreamingFallback ? nonStreamingHardTimeoutMs : initialHardTimeoutMs;
583
592
  hardTimer = setTimeout(() => {
584
593
  diag("hard_timeout_fired", {
585
594
  events: typeof streamEventCount !== "undefined" ? streamEventCount : 0,
@@ -674,6 +683,8 @@ async function* agentLoop(messages, options) {
674
683
  if (firstProviderEventAt === void 0) firstProviderEventAt = pullTime;
675
684
  eventTypeCounts[event.type] = (eventTypeCounts[event.type] ?? 0) + 1;
676
685
  lastEventType = event.type;
686
+ if (event.type === "toolcall_delta") toolInputOpen = true;
687
+ else if (event.type === "toolcall_done") toolInputOpen = false;
677
688
  if ((event.type === "text_delta" || event.type === "server_toolcall" || event.type === "toolcall_delta") && !hasReceivedEvent) {
678
689
  hasReceivedEvent = true;
679
690
  if (hardTimer && hardTimeoutMs < STREAM_OUTPUT_HARD_TIMEOUT_MS) {
@@ -1008,7 +1019,7 @@ async function* agentLoop(messages, options) {
1008
1019
  attempt: stallRetries,
1009
1020
  maxAttempts: MAX_STALL_RETRIES,
1010
1021
  delayMs,
1011
- silent: stallRetries <= 2,
1022
+ silent: stallRetries <= 2 && Date.now() - streamCallStart < VISIBLE_STALL_AFTER_MS,
1012
1023
  ...preservedChars > 0 ? { preservedChars } : {}
1013
1024
  };
1014
1025
  await abortableSleep(delayMs, options.signal);
@@ -1271,7 +1282,8 @@ async function* agentLoop(messages, options) {
1271
1282
  maxTurnToolResultChars: options.maxTurnToolResultChars,
1272
1283
  toolMap,
1273
1284
  invalidToolArgumentCounts,
1274
- markFatalToolArgumentError
1285
+ markFatalToolArgumentError,
1286
+ seenToolCalls: /* @__PURE__ */ new Set()
1275
1287
  };
1276
1288
  const hasSequentialToolCall = toolCalls.some(
1277
1289
  (toolCall) => toolMap.get(toolCall.name)?.executionMode === "sequential"
@@ -1392,6 +1404,15 @@ async function* agentLoop(messages, options) {
1392
1404
  totalUsage: { ...totalUsage }
1393
1405
  };
1394
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
+ }
1395
1416
  function pushToolEvent(eventStream, state, event) {
1396
1417
  if (!state.finalized) eventStream.push(event);
1397
1418
  }
@@ -1408,6 +1429,21 @@ async function executeSingleToolCall(toolCall, options, pushEvent) {
1408
1429
  let isError = false;
1409
1430
  let invalidArgAttempt;
1410
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
+ }
1411
1447
  if (!tool) {
1412
1448
  resultContent = `Unknown tool: ${toolCall.name}`;
1413
1449
  isError = true;
@@ -1442,8 +1478,8 @@ async function executeSingleToolCall(toolCall, options, pushEvent) {
1442
1478
  ctx.signal
1443
1479
  );
1444
1480
  const normalized = normalizeToolResult(raw);
1445
- resultContent = (0, import_ai.redactValue)(normalized.content);
1446
- details = (0, import_ai.redactValue)(normalized.details);
1481
+ resultContent = (0, import_ai.redactValue)(normalized.content, toolRedactionOptions());
1482
+ details = (0, import_ai.redactValue)(normalized.details, toolRedactionOptions());
1447
1483
  for (const key of options.invalidToolArgumentCounts.keys()) {
1448
1484
  if (key.startsWith(`${toolCall.name}:`)) options.invalidToolArgumentCounts.delete(key);
1449
1485
  }
@@ -1474,12 +1510,15 @@ async function executeSingleToolCall(toolCall, options, pushEvent) {
1474
1510
  } else if (options.signal?.aborted && isAbortError(err)) {
1475
1511
  resultContent = indeterminateOutcomeText(toolCall.name);
1476
1512
  } else {
1477
- resultContent = (0, import_ai.redactValue)(err instanceof Error ? err.message : String(err));
1513
+ resultContent = (0, import_ai.redactValue)(
1514
+ err instanceof Error ? err.message : String(err),
1515
+ toolRedactionOptions()
1516
+ );
1478
1517
  }
1479
1518
  }
1480
1519
  }
1481
- resultContent = (0, import_ai.redactValue)(resultContent);
1482
- details = (0, import_ai.redactValue)(details);
1520
+ resultContent = (0, import_ai.redactValue)(resultContent, toolRedactionOptions());
1521
+ details = (0, import_ai.redactValue)(details, toolRedactionOptions());
1483
1522
  const durationMs = Date.now() - startTime;
1484
1523
  pushEvent({
1485
1524
  type: "tool_call_end",
@@ -1521,6 +1560,11 @@ async function* executeToolCallsMixed(toolCalls, initialToolResults, options) {
1521
1560
  for (const phase of phases) {
1522
1561
  if (options.signal?.aborted) break;
1523
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();
1524
1568
  dispatchedIds.add(phase.sequential.id);
1525
1569
  const record = await executeSingleToolCall(
1526
1570
  phase.sequential,