@prestyj/agent 5.6.0 → 5.8.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
@@ -154,7 +154,7 @@ function classifyOverload(err) {
154
154
  if (statusCode === 529 || msg.includes("overloaded") || msg.includes("529")) {
155
155
  return "overloaded";
156
156
  }
157
- if (statusCode === 500 || statusCode === 502 || statusCode === 503 || statusCode === 504 || msg.includes("api_error") || msg.includes("server_error") || msg.includes("internal server error") || msg.includes("bad gateway") || msg.includes("service unavailable") || msg.includes("gateway timeout")) {
157
+ if (statusCode === 500 || statusCode === 502 || statusCode === 503 || statusCode === 504 || statusCode === 507 || msg.includes("api_error") || msg.includes("server_error") || msg.includes("internal server error") || msg.includes("bad gateway") || msg.includes("service unavailable") || msg.includes("gateway timeout") || msg.includes("exceeded request buffer limit while retrying upstream")) {
158
158
  return "provider_error";
159
159
  }
160
160
  if (isOpaqueProviderMessage(err.message)) {
@@ -260,6 +260,8 @@ async function* agentLoop(messages, options) {
260
260
  const maxContinuations = options.maxContinuations ?? 5;
261
261
  let toolMap = new Map((options.tools ?? []).map((t) => [t.name, t]));
262
262
  const totalUsage = { inputTokens: 0, outputTokens: 0 };
263
+ let latestProviderUsage;
264
+ let usageAnchorIndex;
263
265
  let turn = 0;
264
266
  let hitMaxTurns = false;
265
267
  let firstTurn = true;
@@ -296,10 +298,14 @@ async function* agentLoop(messages, options) {
296
298
  const initialHardTimeoutMs = isSakana ? STREAM_THINKING_HARD_TIMEOUT_MS : STREAM_HARD_TIMEOUT_MS;
297
299
  const MAX_TOOLCALL_DELTA_CHARS = 1e6;
298
300
  const MAX_TOOLCALL_DELTA_EVENTS = 2e4;
301
+ let logicalTurnStartedAt = 0;
302
+ let firstProviderEventAt;
303
+ let providerDurationMs = 0;
299
304
  try {
300
305
  while (turn < maxTurns) {
301
306
  options.signal?.throwIfAborted();
302
307
  turn++;
308
+ if (logicalTurnStartedAt === 0) logicalTurnStartedAt = Date.now();
303
309
  toolMap = new Map((options.tools ?? []).map((t) => [t.name, t]));
304
310
  if (_diagFn) {
305
311
  let msgChars = 0;
@@ -332,7 +338,11 @@ async function* agentLoop(messages, options) {
332
338
  firstTurn = false;
333
339
  if (options.transformContext) {
334
340
  diag("transform_start");
335
- const transformed = await options.transformContext(messages);
341
+ const pendingMessages = usageAnchorIndex === void 0 ? [] : messages.slice(usageAnchorIndex + 1);
342
+ const transformed = await options.transformContext(messages, {
343
+ usage: latestProviderUsage,
344
+ pendingMessages
345
+ });
336
346
  if (transformed !== messages) {
337
347
  diag("transform_compacted", {
338
348
  before: messages.length,
@@ -340,6 +350,8 @@ async function* agentLoop(messages, options) {
340
350
  });
341
351
  messages.length = 0;
342
352
  messages.push(...transformed);
353
+ latestProviderUsage = void 0;
354
+ usageAnchorIndex = void 0;
343
355
  }
344
356
  diag("transform_end");
345
357
  }
@@ -349,6 +361,7 @@ async function* agentLoop(messages, options) {
349
361
  let idleTimer = null;
350
362
  let hardTimer = null;
351
363
  let idleTimedOut = false;
364
+ let providerAttemptStartedAt;
352
365
  let streamEventCount = 0;
353
366
  let lastEventTime = Date.now();
354
367
  let streamCallStart = Date.now();
@@ -394,12 +407,14 @@ async function* agentLoop(messages, options) {
394
407
  try {
395
408
  diag("stream_call", { nonStreaming: useNonStreamingFallback });
396
409
  streamCallStart = Date.now();
410
+ providerAttemptStartedAt = streamCallStart;
397
411
  const result = (0, import_ai.stream)({
398
412
  provider: options.provider,
399
413
  model: options.model,
400
414
  messages,
401
415
  tools: options.tools,
402
416
  serverTools: options.serverTools,
417
+ toolChoice: options.toolChoice,
403
418
  webSearch: options.webSearch,
404
419
  maxTokens: options.maxTokens,
405
420
  temperature: options.temperature,
@@ -442,6 +457,7 @@ async function* agentLoop(messages, options) {
442
457
  maxConsumerLagMs = consumerLag;
443
458
  }
444
459
  streamEventCount++;
460
+ if (firstProviderEventAt === void 0) firstProviderEventAt = pullTime;
445
461
  eventTypeCounts[event.type] = (eventTypeCounts[event.type] ?? 0) + 1;
446
462
  lastEventType = event.type;
447
463
  if ((event.type === "text_delta" || event.type === "server_toolcall" || event.type === "toolcall_delta") && !hasReceivedEvent) {
@@ -534,6 +550,7 @@ async function* agentLoop(messages, options) {
534
550
  eventTypes: eventTypeCounts
535
551
  });
536
552
  response = await abortablePromise(result.response, streamController.signal);
553
+ if (firstProviderEventAt === void 0) firstProviderEventAt = Date.now();
537
554
  } catch (err) {
538
555
  if (streamController.signal.aborted) closeIterator(streamIterator);
539
556
  const errMsg = err instanceof Error ? err.message : String(err);
@@ -595,10 +612,17 @@ async function* agentLoop(messages, options) {
595
612
  ...overflowDetails
596
613
  });
597
614
  try {
598
- const compacted = await options.transformContext(messages, { force: true });
615
+ const pendingMessages = usageAnchorIndex === void 0 ? [] : messages.slice(usageAnchorIndex + 1);
616
+ const compacted = await options.transformContext(messages, {
617
+ force: true,
618
+ usage: latestProviderUsage,
619
+ pendingMessages
620
+ });
599
621
  if (compacted !== messages && compacted.length < messages.length) {
600
622
  messages.length = 0;
601
623
  messages.push(...compacted);
624
+ latestProviderUsage = void 0;
625
+ usageAnchorIndex = void 0;
602
626
  diag("overflow_compact_success", {
603
627
  attempt: overflowCompactionAttempts,
604
628
  messages: messages.length,
@@ -758,6 +782,9 @@ async function* agentLoop(messages, options) {
758
782
  });
759
783
  throw err;
760
784
  } finally {
785
+ if (providerAttemptStartedAt !== void 0) {
786
+ providerDurationMs += Date.now() - providerAttemptStartedAt;
787
+ }
761
788
  if (idleTimer) clearTimeout(idleTimer);
762
789
  if (hardTimer) clearTimeout(hardTimer);
763
790
  options.signal?.removeEventListener("abort", forwardAbort);
@@ -801,11 +828,29 @@ async function* agentLoop(messages, options) {
801
828
  totalUsage.cacheWrite = (totalUsage.cacheWrite ?? 0) + response.usage.cacheWrite;
802
829
  }
803
830
  messages.push(response.message);
831
+ latestProviderUsage = response.usage;
832
+ usageAnchorIndex = messages.length - 1;
833
+ const completedAt = Date.now();
834
+ const outputTokensPerSecond = providerDurationMs > 0 && response.usage.outputTokens > 0 ? response.usage.outputTokens / (providerDurationMs / 1e3) : void 0;
835
+ const timing = {
836
+ startedAt: logicalTurnStartedAt,
837
+ ...firstProviderEventAt !== void 0 ? {
838
+ firstProviderEventAt,
839
+ ttftMs: Math.max(0, firstProviderEventAt - logicalTurnStartedAt)
840
+ } : {},
841
+ completedAt,
842
+ providerDurationMs,
843
+ ...outputTokensPerSecond !== void 0 ? { outputTokensPerSecond } : {}
844
+ };
845
+ logicalTurnStartedAt = 0;
846
+ firstProviderEventAt = void 0;
847
+ providerDurationMs = 0;
804
848
  yield {
805
849
  type: "turn_end",
806
850
  turn,
807
851
  stopReason: response.stopReason,
808
- usage: response.usage
852
+ usage: response.usage,
853
+ timing
809
854
  };
810
855
  if (response.stopReason === "pause_turn") {
811
856
  consecutivePauses++;
@@ -872,6 +917,7 @@ async function* agentLoop(messages, options) {
872
917
  const executionOptions = {
873
918
  signal: options.signal,
874
919
  maxToolResultChars: options.maxToolResultChars,
920
+ maxTurnToolResultChars: options.maxTurnToolResultChars,
875
921
  toolMap,
876
922
  invalidToolArgumentCounts,
877
923
  markFatalToolArgumentError
@@ -989,8 +1035,8 @@ async function executeSingleToolCall(toolCall, options, pushEvent) {
989
1035
  ctx.signal
990
1036
  );
991
1037
  const normalized = normalizeToolResult(raw);
992
- resultContent = normalized.content;
993
- details = normalized.details;
1038
+ resultContent = (0, import_ai.redactValue)(normalized.content);
1039
+ details = (0, import_ai.redactValue)(normalized.details);
994
1040
  for (const key of options.invalidToolArgumentCounts.keys()) {
995
1041
  if (key.startsWith(`${toolCall.name}:`)) options.invalidToolArgumentCounts.delete(key);
996
1042
  }
@@ -1018,10 +1064,12 @@ async function executeSingleToolCall(toolCall, options, pushEvent) {
1018
1064
  );
1019
1065
  }
1020
1066
  } else {
1021
- resultContent = err instanceof Error ? err.message : String(err);
1067
+ resultContent = (0, import_ai.redactValue)(err instanceof Error ? err.message : String(err));
1022
1068
  }
1023
1069
  }
1024
1070
  }
1071
+ resultContent = (0, import_ai.redactValue)(resultContent);
1072
+ details = (0, import_ai.redactValue)(details);
1025
1073
  const durationMs = Date.now() - startTime;
1026
1074
  pushEvent({
1027
1075
  type: "tool_call_end",
@@ -1109,6 +1157,7 @@ async function* executeToolCallsMixed(toolCalls, initialToolResults, options) {
1109
1157
  }
1110
1158
  const toolResults = buildToolResults(initialToolResults, toolCalls, resultsById);
1111
1159
  capToolResults(toolResults, options.maxToolResultChars);
1160
+ capTurnToolResults(toolResults, options.maxTurnToolResultChars);
1112
1161
  return { toolResults, aborted };
1113
1162
  }
1114
1163
  async function* executeToolCallsParallel(toolCalls, initialToolResults, options) {
@@ -1148,6 +1197,7 @@ async function* executeToolCallsParallel(toolCalls, initialToolResults, options)
1148
1197
  }
1149
1198
  const toolResults = buildToolResults(initialToolResults, toolCalls, resultsById);
1150
1199
  capToolResults(toolResults, options.maxToolResultChars);
1200
+ capTurnToolResults(toolResults, options.maxTurnToolResultChars);
1151
1201
  return { toolResults, aborted };
1152
1202
  }
1153
1203
  function buildToolResults(initialToolResults, toolCalls, resultsById) {
@@ -1190,6 +1240,34 @@ function capToolResults(toolResults, maxToolResultChars) {
1190
1240
  ` + tail;
1191
1241
  }
1192
1242
  }
1243
+ function capTurnToolResults(toolResults, maxTurnToolResultChars) {
1244
+ if (!maxTurnToolResultChars) return;
1245
+ const textResults = toolResults.filter(
1246
+ (toolResult) => typeof toolResult.content === "string"
1247
+ );
1248
+ const total = textResults.reduce((sum, toolResult) => sum + toolResult.content.length, 0);
1249
+ if (total <= maxTurnToolResultChars) return;
1250
+ const bySize = [...textResults].sort((a, b) => a.content.length - b.content.length);
1251
+ let remaining = maxTurnToolResultChars;
1252
+ let left = bySize.length;
1253
+ for (const toolResult of bySize) {
1254
+ const fairShare = Math.floor(remaining / left);
1255
+ left--;
1256
+ if (toolResult.content.length <= fairShare) {
1257
+ remaining -= toolResult.content.length;
1258
+ continue;
1259
+ }
1260
+ remaining -= fairShare;
1261
+ const headChars = Math.floor(fairShare * 0.7);
1262
+ const tailChars = fairShare - headChars;
1263
+ const omitted = toolResult.content.length - fairShare;
1264
+ toolResult.content = toolResult.content.slice(0, headChars) + `
1265
+
1266
+ [... ${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
+
1268
+ ` + (tailChars > 0 ? toolResult.content.slice(-tailChars) : "");
1269
+ }
1270
+ }
1193
1271
  function normalizeToolResult(raw) {
1194
1272
  return typeof raw === "string" ? { content: raw } : raw;
1195
1273
  }