@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.js CHANGED
@@ -7,6 +7,7 @@ import {
7
7
  stream,
8
8
  EventStream,
9
9
  EZCoderAIError,
10
+ environmentSecrets,
10
11
  isHardBillingMessage,
11
12
  redactValue,
12
13
  sliceHead,
@@ -92,6 +93,11 @@ function clampOutputTokens(key, requested) {
92
93
  // src/agent-loop.ts
93
94
  var DEFAULT_MAX_TURNS = 300;
94
95
  var DEFAULT_TOOL_TIMEOUT_MS = 3e5;
96
+ var _toolRedaction;
97
+ function toolRedactionOptions() {
98
+ _toolRedaction ??= { secrets: environmentSecrets(process.env) };
99
+ return _toolRedaction;
100
+ }
95
101
  var _diagFn = null;
96
102
  function setStreamDiagnostic(fn) {
97
103
  _diagFn = fn;
@@ -427,11 +433,14 @@ async function* agentLoop(messages, options) {
427
433
  const OVERLOAD_BASE_DELAY_MS = 2e3;
428
434
  const OVERLOAD_MAX_DELAY_MS = 3e4;
429
435
  const STREAM_IDLE_TIMEOUT_MS = 9e4;
436
+ const STREAM_TOOL_INPUT_IDLE_TIMEOUT_MS = 3e5;
430
437
  const STREAM_HARD_TIMEOUT_MS = 9e4;
431
438
  const STREAM_OUTPUT_HARD_TIMEOUT_MS = 3e5;
432
- const STREAM_THINKING_IDLE_TIMEOUT_MS = 3e5;
433
- const STREAM_THINKING_HARD_TIMEOUT_MS = 6e5;
439
+ const STREAM_THINKING_IDLE_TIMEOUT_MS = 6e5;
440
+ const STREAM_THINKING_HARD_TIMEOUT_MS = 9e5;
434
441
  const NON_STREAMING_HARD_TIMEOUT_MS = 3e5;
442
+ const nonStreamingHardTimeoutMs = options.thinking != null ? STREAM_THINKING_HARD_TIMEOUT_MS : NON_STREAMING_HARD_TIMEOUT_MS;
443
+ const VISIBLE_STALL_AFTER_MS = 6e4;
435
444
  const usesSilentReasoningBudget = options.provider === "sakana" || options.provider === "openai" && options.thinking != null;
436
445
  const localBackend = isLocalBackendUrl(options.baseUrl);
437
446
  const baseFirstEventTimeoutMs = localBackend ? Number.POSITIVE_INFINITY : usesSilentReasoningBudget ? STREAM_THINKING_IDLE_TIMEOUT_MS : STREAM_FIRST_EVENT_TIMEOUT_MS;
@@ -533,10 +542,11 @@ async function* agentLoop(messages, options) {
533
542
  options.signal?.addEventListener("abort", forwardAbort, { once: true });
534
543
  let hasReceivedEvent = false;
535
544
  let hasReceivedThinking = false;
545
+ let toolInputOpen = false;
536
546
  const resetIdleTimer = () => {
537
547
  if (useNonStreamingFallback) return;
538
548
  if (idleTimer) clearTimeout(idleTimer);
539
- const timeoutMs = hasReceivedEvent ? STREAM_IDLE_TIMEOUT_MS : hasReceivedThinking ? STREAM_THINKING_IDLE_TIMEOUT_MS : firstEventTimeoutMs;
549
+ const timeoutMs = hasReceivedEvent ? toolInputOpen ? STREAM_TOOL_INPUT_IDLE_TIMEOUT_MS : STREAM_IDLE_TIMEOUT_MS : hasReceivedThinking ? STREAM_THINKING_IDLE_TIMEOUT_MS : firstEventTimeoutMs;
540
550
  if (!Number.isFinite(timeoutMs)) return;
541
551
  idleTimer = setTimeout(() => {
542
552
  diag("idle_timeout_fired", {
@@ -544,14 +554,14 @@ async function* agentLoop(messages, options) {
544
554
  sinceLastEventMs: Date.now() - lastEventTime,
545
555
  lastEventType,
546
556
  maxConsumerLagMs,
547
- phase: hasReceivedEvent ? "mid_stream" : hasReceivedThinking ? "post_thinking" : "first_event",
557
+ phase: hasReceivedEvent ? toolInputOpen ? "mid_tool_input" : "mid_stream" : hasReceivedThinking ? "post_thinking" : "first_event",
548
558
  eventTypes: eventTypeCounts
549
559
  });
550
560
  idleTimedOut = true;
551
561
  streamController.abort();
552
562
  }, timeoutMs);
553
563
  };
554
- let hardTimeoutMs = useNonStreamingFallback ? NON_STREAMING_HARD_TIMEOUT_MS : initialHardTimeoutMs;
564
+ let hardTimeoutMs = useNonStreamingFallback ? nonStreamingHardTimeoutMs : initialHardTimeoutMs;
555
565
  hardTimer = setTimeout(() => {
556
566
  diag("hard_timeout_fired", {
557
567
  events: typeof streamEventCount !== "undefined" ? streamEventCount : 0,
@@ -646,6 +656,8 @@ async function* agentLoop(messages, options) {
646
656
  if (firstProviderEventAt === void 0) firstProviderEventAt = pullTime;
647
657
  eventTypeCounts[event.type] = (eventTypeCounts[event.type] ?? 0) + 1;
648
658
  lastEventType = event.type;
659
+ if (event.type === "toolcall_delta") toolInputOpen = true;
660
+ else if (event.type === "toolcall_done") toolInputOpen = false;
649
661
  if ((event.type === "text_delta" || event.type === "server_toolcall" || event.type === "toolcall_delta") && !hasReceivedEvent) {
650
662
  hasReceivedEvent = true;
651
663
  if (hardTimer && hardTimeoutMs < STREAM_OUTPUT_HARD_TIMEOUT_MS) {
@@ -980,7 +992,7 @@ async function* agentLoop(messages, options) {
980
992
  attempt: stallRetries,
981
993
  maxAttempts: MAX_STALL_RETRIES,
982
994
  delayMs,
983
- silent: stallRetries <= 2,
995
+ silent: stallRetries <= 2 && Date.now() - streamCallStart < VISIBLE_STALL_AFTER_MS,
984
996
  ...preservedChars > 0 ? { preservedChars } : {}
985
997
  };
986
998
  await abortableSleep(delayMs, options.signal);
@@ -1243,7 +1255,8 @@ async function* agentLoop(messages, options) {
1243
1255
  maxTurnToolResultChars: options.maxTurnToolResultChars,
1244
1256
  toolMap,
1245
1257
  invalidToolArgumentCounts,
1246
- markFatalToolArgumentError
1258
+ markFatalToolArgumentError,
1259
+ seenToolCalls: /* @__PURE__ */ new Set()
1247
1260
  };
1248
1261
  const hasSequentialToolCall = toolCalls.some(
1249
1262
  (toolCall) => toolMap.get(toolCall.name)?.executionMode === "sequential"
@@ -1364,6 +1377,15 @@ async function* agentLoop(messages, options) {
1364
1377
  totalUsage: { ...totalUsage }
1365
1378
  };
1366
1379
  }
1380
+ function canonicalToolArgs(value) {
1381
+ if (Array.isArray(value)) return value.map(canonicalToolArgs);
1382
+ if (value !== null && typeof value === "object") {
1383
+ return Object.fromEntries(
1384
+ Object.entries(value).sort(([a], [b]) => a.localeCompare(b)).map(([k, v]) => [k, canonicalToolArgs(v)])
1385
+ );
1386
+ }
1387
+ return value;
1388
+ }
1367
1389
  function pushToolEvent(eventStream, state, event) {
1368
1390
  if (!state.finalized) eventStream.push(event);
1369
1391
  }
@@ -1380,6 +1402,21 @@ async function executeSingleToolCall(toolCall, options, pushEvent) {
1380
1402
  let isError = false;
1381
1403
  let invalidArgAttempt;
1382
1404
  const tool = options.toolMap.get(toolCall.name);
1405
+ if (tool) {
1406
+ const signature = JSON.stringify([toolCall.name, canonicalToolArgs(toolCall.args)]);
1407
+ if (options.seenToolCalls.has(signature)) {
1408
+ const content = "Tool call cancelled: an identical call already appeared in this response; this call was not executed.";
1409
+ pushEvent({
1410
+ type: "tool_call_end",
1411
+ toolCallId: toolCall.id,
1412
+ result: content,
1413
+ isError: true,
1414
+ durationMs: Date.now() - startTime
1415
+ });
1416
+ return { toolCallId: toolCall.id, content, isError: true };
1417
+ }
1418
+ options.seenToolCalls.add(signature);
1419
+ }
1383
1420
  if (!tool) {
1384
1421
  resultContent = `Unknown tool: ${toolCall.name}`;
1385
1422
  isError = true;
@@ -1414,8 +1451,8 @@ async function executeSingleToolCall(toolCall, options, pushEvent) {
1414
1451
  ctx.signal
1415
1452
  );
1416
1453
  const normalized = normalizeToolResult(raw);
1417
- resultContent = redactValue(normalized.content);
1418
- details = redactValue(normalized.details);
1454
+ resultContent = redactValue(normalized.content, toolRedactionOptions());
1455
+ details = redactValue(normalized.details, toolRedactionOptions());
1419
1456
  for (const key of options.invalidToolArgumentCounts.keys()) {
1420
1457
  if (key.startsWith(`${toolCall.name}:`)) options.invalidToolArgumentCounts.delete(key);
1421
1458
  }
@@ -1446,12 +1483,15 @@ async function executeSingleToolCall(toolCall, options, pushEvent) {
1446
1483
  } else if (options.signal?.aborted && isAbortError(err)) {
1447
1484
  resultContent = indeterminateOutcomeText(toolCall.name);
1448
1485
  } else {
1449
- resultContent = redactValue(err instanceof Error ? err.message : String(err));
1486
+ resultContent = redactValue(
1487
+ err instanceof Error ? err.message : String(err),
1488
+ toolRedactionOptions()
1489
+ );
1450
1490
  }
1451
1491
  }
1452
1492
  }
1453
- resultContent = redactValue(resultContent);
1454
- details = redactValue(details);
1493
+ resultContent = redactValue(resultContent, toolRedactionOptions());
1494
+ details = redactValue(details, toolRedactionOptions());
1455
1495
  const durationMs = Date.now() - startTime;
1456
1496
  pushEvent({
1457
1497
  type: "tool_call_end",
@@ -1493,6 +1533,11 @@ async function* executeToolCallsMixed(toolCalls, initialToolResults, options) {
1493
1533
  for (const phase of phases) {
1494
1534
  if (options.signal?.aborted) break;
1495
1535
  if (phase.sequential) {
1536
+ const signature = JSON.stringify([
1537
+ phase.sequential.name,
1538
+ canonicalToolArgs(phase.sequential.args)
1539
+ ]);
1540
+ if (!options.seenToolCalls.has(signature)) options.seenToolCalls.clear();
1496
1541
  dispatchedIds.add(phase.sequential.id);
1497
1542
  const record = await executeSingleToolCall(
1498
1543
  phase.sequential,