@prestyj/agent 4.3.161 → 4.3.200

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
@@ -34,6 +34,7 @@ module.exports = __toCommonJS(index_exports);
34
34
  var import_ai2 = require("@prestyj/ai");
35
35
 
36
36
  // src/agent-loop.ts
37
+ var import_zod = require("zod");
37
38
  var import_ai = require("@prestyj/ai");
38
39
  var DEFAULT_MAX_TURNS = 200;
39
40
  var _diagFn = null;
@@ -55,6 +56,44 @@ function isContextOverflow(err) {
55
56
  const msg = err.message.toLowerCase();
56
57
  return msg.includes("prompt is too long") || msg.includes("prompt too long") || msg.includes("input is too long") || msg.includes("context_length_exceeded") || msg.includes("context_window_exceeded") || msg.includes("maximum context length") || msg.includes("exceeds model context window") || msg.includes("exceeds the context window") || msg.includes("content_too_large") || msg.includes("request_too_large") || msg.includes("reduce the length") || msg.includes("please shorten") || msg.includes("token") && msg.includes("exceed");
57
58
  }
59
+ function parseOverflowNumber(value) {
60
+ return Number(value.replace(/[,_\s]/g, ""));
61
+ }
62
+ function extractContextOverflowDetails(err) {
63
+ if (!(err instanceof Error)) return {};
64
+ const text = err.message;
65
+ const patterns = [
66
+ // Anthropic/OpenAI-compatible: "203456 tokens > 200000 maximum"
67
+ {
68
+ regex: /([\d,_.\s]+)\s*tokens?\s*>\s*([\d,_.\s]+)\s*(?:maximum|max|limit)?/i,
69
+ tokensGroup: 1,
70
+ limitGroup: 2
71
+ },
72
+ // OpenAI: "maximum context length is 128000 tokens ... resulted in 130000 tokens"
73
+ {
74
+ regex: /maximum context length is\s*([\d,_.\s]+)\s*tokens?[\s\S]*?resulted in\s*([\d,_.\s]+)\s*tokens?/i,
75
+ tokensGroup: 2,
76
+ limitGroup: 1
77
+ },
78
+ // Generic: "130000 input tokens exceeds 128000 token limit"
79
+ {
80
+ regex: /([\d,_.\s]+)\s*(?:input\s*)?tokens?[\s\S]{0,80}?exceeds?[\s\S]{0,80}?([\d,_.\s]+)\s*(?:token\s*)?(?:limit|maximum|max)/i,
81
+ tokensGroup: 1,
82
+ limitGroup: 2
83
+ }
84
+ ];
85
+ for (const pattern of patterns) {
86
+ const match = text.match(pattern.regex);
87
+ if (!match) continue;
88
+ const observedTokens = parseOverflowNumber(match[pattern.tokensGroup] ?? "");
89
+ const observedLimit = parseOverflowNumber(match[pattern.limitGroup] ?? "");
90
+ return {
91
+ ...Number.isFinite(observedTokens) && observedTokens > 0 ? { observedTokens } : {},
92
+ ...Number.isFinite(observedLimit) && observedLimit > 0 ? { observedLimit } : {}
93
+ };
94
+ }
95
+ return {};
96
+ }
58
97
  function isBillingError(err) {
59
98
  if (!(err instanceof Error)) return false;
60
99
  const msg = err.message.toLowerCase();
@@ -66,11 +105,22 @@ function isToolPairingError(err) {
66
105
  return msg.includes("tool_use") && msg.includes("tool_result") || msg.includes("unexpected `tool_use_id`") || msg.includes("tool_use ids found without") || // Moonshot/OpenAI-compatible: "tool call id <id> is not found"
67
106
  msg.includes("tool call id") && msg.includes("is not found");
68
107
  }
69
- function isOverloaded(err) {
70
- if (!(err instanceof Error)) return false;
71
- if (isBillingError(err)) return false;
108
+ function classifyOverload(err) {
109
+ if (!(err instanceof Error)) return null;
110
+ if (isBillingError(err)) return null;
72
111
  const msg = err.message.toLowerCase();
73
- return msg.includes("overloaded") || msg.includes("rate limit") || msg.includes("too many requests") || msg.includes("429") || msg.includes("529");
112
+ const errorWithStatus = err;
113
+ const statusCode = typeof errorWithStatus.statusCode === "number" ? errorWithStatus.statusCode : void 0;
114
+ if (statusCode === 429 || msg.includes("rate_limit") || msg.includes("rate limit") || msg.includes("too many requests") || msg.includes("429")) {
115
+ return "rate_limit";
116
+ }
117
+ if (statusCode === 529 || msg.includes("overloaded") || msg.includes("529")) {
118
+ return "overloaded";
119
+ }
120
+ 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")) {
121
+ return "provider_error";
122
+ }
123
+ return null;
74
124
  }
75
125
  function isMalformedStream(err) {
76
126
  if (!(err instanceof Error)) return false;
@@ -80,6 +130,46 @@ function isMalformedStream(err) {
80
130
  const msg = err.message;
81
131
  return /\bin JSON at position \d+/i.test(msg);
82
132
  }
133
+ function isTransportFailure(err) {
134
+ const codes = /* @__PURE__ */ new Set([
135
+ "ECONNRESET",
136
+ "ECONNREFUSED",
137
+ "ECONNABORTED",
138
+ "ETIMEDOUT",
139
+ "EPIPE",
140
+ "EHOSTUNREACH",
141
+ "ENETUNREACH",
142
+ "ENOTFOUND",
143
+ "UND_ERR_SOCKET",
144
+ "UND_ERR_CONNECT_TIMEOUT",
145
+ "UND_ERR_HEADERS_TIMEOUT",
146
+ "UND_ERR_BODY_TIMEOUT",
147
+ "UND_ERR_RESPONSE_STATUS_CODE",
148
+ "UND_ERR_REQ_CONTENT_LENGTH_MISMATCH",
149
+ "UND_ERR_RES_CONTENT_LENGTH_MISMATCH"
150
+ ]);
151
+ const messages = [
152
+ /^terminated$/i,
153
+ /\bother side closed\b/i,
154
+ /\bsocket hang up\b/i,
155
+ /\bfetch failed\b/i,
156
+ /\bbody timeout error\b/i,
157
+ /\bsse stream disconnected\b/i,
158
+ /\bfailed to reconnect sse stream\b/i
159
+ ];
160
+ const seen = /* @__PURE__ */ new Set();
161
+ let cur = err;
162
+ while (cur && typeof cur === "object" && !seen.has(cur)) {
163
+ seen.add(cur);
164
+ const e = cur;
165
+ if (typeof e.code === "string" && codes.has(e.code)) return true;
166
+ if (typeof e.message === "string") {
167
+ for (const re of messages) if (re.test(e.message)) return true;
168
+ }
169
+ cur = e.cause;
170
+ }
171
+ return false;
172
+ }
83
173
  function abortableSleep(ms, signal) {
84
174
  if (signal?.aborted) {
85
175
  return Promise.reject(new DOMException("Aborted", "AbortError"));
@@ -110,6 +200,8 @@ async function* agentLoop(messages, options) {
110
200
  let emptyResponseRetries = 0;
111
201
  let stallRetries = 0;
112
202
  let overflowCompactionAttempts = 0;
203
+ let toolResultTruncationAttempted = false;
204
+ const invalidToolArgumentCounts = /* @__PURE__ */ new Map();
113
205
  let useNonStreamingFallback = false;
114
206
  const MAX_OVERLOAD_RETRIES = 10;
115
207
  const MAX_EMPTY_RESPONSE_RETRIES = 2;
@@ -126,6 +218,8 @@ async function* agentLoop(messages, options) {
126
218
  const STREAM_THINKING_IDLE_TIMEOUT_MS = 3e5;
127
219
  const STREAM_THINKING_HARD_TIMEOUT_MS = 6e5;
128
220
  const NON_STREAMING_HARD_TIMEOUT_MS = 3e5;
221
+ const MAX_TOOLCALL_DELTA_CHARS = 1e6;
222
+ const MAX_TOOLCALL_DELTA_EVENTS = 2e4;
129
223
  try {
130
224
  while (turn < maxTurns) {
131
225
  options.signal?.throwIfAborted();
@@ -181,6 +275,9 @@ async function* agentLoop(messages, options) {
181
275
  let streamCallStart = Date.now();
182
276
  const eventTypeCounts = {};
183
277
  let lastEventType = "";
278
+ let toolcallDeltaChars = 0;
279
+ let toolcallDeltaCount = 0;
280
+ let runawayDetected = null;
184
281
  let lastYieldEndTime = Date.now();
185
282
  let maxConsumerLagMs = 0;
186
283
  const forwardAbort = () => streamController.abort();
@@ -231,9 +328,12 @@ async function* agentLoop(messages, options) {
231
328
  signal: streamController.signal,
232
329
  accountId: options.accountId,
233
330
  cacheRetention: options.cacheRetention,
331
+ promptCacheKey: options.promptCacheKey,
332
+ serviceTier: options.serviceTier,
234
333
  supportsImages: options.supportsImages,
235
334
  compaction: options.compaction,
236
335
  clearToolUses: options.clearToolUses,
336
+ userAgent: options.userAgent,
237
337
  // Flip to non-streaming fallback after repeated stream stalls.
238
338
  ...useNonStreamingFallback ? { streaming: false } : {}
239
339
  });
@@ -244,11 +344,14 @@ async function* agentLoop(messages, options) {
244
344
  hasReceivedEvent = false;
245
345
  lastEventTime = Date.now();
246
346
  streamCallStart = Date.now();
347
+ lastYieldEndTime = Date.now();
247
348
  resetIdleTimer();
248
349
  for await (const event of result) {
249
350
  const pullTime = Date.now();
250
351
  const consumerLag = pullTime - lastYieldEndTime;
251
- if (consumerLag > maxConsumerLagMs) maxConsumerLagMs = consumerLag;
352
+ if (streamEventCount > 0 && consumerLag > maxConsumerLagMs) {
353
+ maxConsumerLagMs = consumerLag;
354
+ }
252
355
  streamEventCount++;
253
356
  eventTypeCounts[event.type] = (eventTypeCounts[event.type] ?? 0) + 1;
254
357
  lastEventType = event.type;
@@ -307,9 +410,25 @@ async function* agentLoop(messages, options) {
307
410
  data: event.data
308
411
  };
309
412
  } else if (event.type === "toolcall_delta") {
413
+ const chunkChars = event.argsJson?.length ?? 0;
414
+ toolcallDeltaChars += chunkChars;
415
+ toolcallDeltaCount++;
416
+ if (!runawayDetected && (toolcallDeltaChars > MAX_TOOLCALL_DELTA_CHARS || toolcallDeltaCount > MAX_TOOLCALL_DELTA_EVENTS)) {
417
+ runawayDetected = {
418
+ kind: toolcallDeltaChars > MAX_TOOLCALL_DELTA_CHARS ? "chars" : "events",
419
+ chars: toolcallDeltaChars,
420
+ events: toolcallDeltaCount
421
+ };
422
+ diag("runaway_toolcall_detected", {
423
+ ...runawayDetected,
424
+ provider: options.provider,
425
+ model: options.model
426
+ });
427
+ streamController.abort();
428
+ }
310
429
  yield {
311
430
  type: "toolcall_delta",
312
- chars: event.argsJson?.length ?? 0
431
+ chars: chunkChars
313
432
  };
314
433
  }
315
434
  lastYieldEndTime = Date.now();
@@ -334,12 +453,44 @@ async function* agentLoop(messages, options) {
334
453
  model: options.model
335
454
  });
336
455
  if (isContextOverflow(err)) {
456
+ const overflowDetails = extractContextOverflowDetails(err);
457
+ diag("context_overflow_detected", {
458
+ ...overflowDetails,
459
+ error: errMsg.slice(0, 500),
460
+ messages: messages.length
461
+ });
462
+ const overflowToolResultMaxChars = Math.min(
463
+ options.maxToolResultChars ?? 1e5,
464
+ 1e5
465
+ );
466
+ if (!toolResultTruncationAttempted) {
467
+ toolResultTruncationAttempted = true;
468
+ const truncated = truncateOversizedToolResults(messages, overflowToolResultMaxChars);
469
+ diag("overflow_tool_result_truncation", {
470
+ truncated,
471
+ maxChars: overflowToolResultMaxChars
472
+ });
473
+ if (truncated) {
474
+ yield {
475
+ type: "retry",
476
+ reason: "overflow_compact",
477
+ attempt: overflowCompactionAttempts + 1,
478
+ maxAttempts: MAX_OVERFLOW_COMPACTIONS,
479
+ delayMs: 0,
480
+ ...overflowDetails,
481
+ silent: true
482
+ };
483
+ turn--;
484
+ continue;
485
+ }
486
+ }
337
487
  if (options.transformContext && overflowCompactionAttempts < MAX_OVERFLOW_COMPACTIONS) {
338
488
  overflowCompactionAttempts++;
339
489
  diag("overflow_compact_start", {
340
490
  attempt: overflowCompactionAttempts,
341
491
  maxAttempts: MAX_OVERFLOW_COMPACTIONS,
342
- messages: messages.length
492
+ messages: messages.length,
493
+ ...overflowDetails
343
494
  });
344
495
  try {
345
496
  const compacted = await options.transformContext(messages, { force: true });
@@ -348,14 +499,16 @@ async function* agentLoop(messages, options) {
348
499
  messages.push(...compacted);
349
500
  diag("overflow_compact_success", {
350
501
  attempt: overflowCompactionAttempts,
351
- messages: messages.length
502
+ messages: messages.length,
503
+ ...overflowDetails
352
504
  });
353
505
  yield {
354
506
  type: "retry",
355
507
  reason: "overflow_compact",
356
508
  attempt: overflowCompactionAttempts,
357
509
  maxAttempts: MAX_OVERFLOW_COMPACTIONS,
358
- delayMs: 0
510
+ delayMs: 0,
511
+ ...overflowDetails
359
512
  };
360
513
  turn--;
361
514
  continue;
@@ -363,32 +516,35 @@ async function* agentLoop(messages, options) {
363
516
  diag("overflow_compact_noop", {
364
517
  attempt: overflowCompactionAttempts,
365
518
  before: messages.length,
366
- after: compacted.length
519
+ after: compacted.length,
520
+ ...overflowDetails
367
521
  });
368
522
  } catch (compactErr) {
369
523
  diag("overflow_compact_failed", {
370
- error: compactErr instanceof Error ? compactErr.message : String(compactErr)
524
+ error: compactErr instanceof Error ? compactErr.message : String(compactErr),
525
+ ...overflowDetails
371
526
  });
372
527
  }
373
528
  }
374
529
  yield { type: "error", error: err instanceof Error ? err : new Error(errMsg) };
375
530
  throw err;
376
531
  }
377
- if (overloadRetries < MAX_OVERLOAD_RETRIES && isOverloaded(err)) {
532
+ const overloadKind = classifyOverload(err);
533
+ if (overloadRetries < MAX_OVERLOAD_RETRIES && overloadKind) {
378
534
  overloadRetries++;
379
535
  const delayMs = Math.min(
380
536
  OVERLOAD_BASE_DELAY_MS * 2 ** (overloadRetries - 1),
381
537
  OVERLOAD_MAX_DELAY_MS
382
538
  );
383
539
  diag("retry", {
384
- reason: "overloaded",
540
+ reason: overloadKind,
385
541
  attempt: overloadRetries,
386
542
  maxAttempts: MAX_OVERLOAD_RETRIES,
387
543
  delayMs
388
544
  });
389
545
  yield {
390
546
  type: "retry",
391
- reason: "overloaded",
547
+ reason: overloadKind,
392
548
  attempt: overloadRetries,
393
549
  maxAttempts: MAX_OVERLOAD_RETRIES,
394
550
  delayMs
@@ -397,22 +553,39 @@ async function* agentLoop(messages, options) {
397
553
  turn--;
398
554
  continue;
399
555
  }
556
+ if (runawayDetected) {
557
+ diag("runaway_toolcall_aborted", {
558
+ ...runawayDetected,
559
+ provider: options.provider,
560
+ model: options.model
561
+ });
562
+ const detail = runawayDetected.kind === "chars" ? `${(runawayDetected.chars / 1024).toFixed(0)} KB of tool-call arguments` : `${runawayDetected.events} tool-call delta events`;
563
+ yield {
564
+ type: "error",
565
+ error: new Error(
566
+ `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.`
567
+ )
568
+ };
569
+ break;
570
+ }
400
571
  const malformed = isMalformedStream(err);
401
- const transportFailure = (idleTimedOut || malformed) && !options.signal?.aborted;
572
+ const socketDrop = isTransportFailure(err);
573
+ const transportFailure = (idleTimedOut || malformed || socketDrop) && !options.signal?.aborted;
402
574
  if (transportFailure && stallRetries < MAX_STALL_RETRIES) {
403
575
  stallRetries++;
576
+ const cause = malformed ? "malformed_stream" : socketDrop ? "socket_drop" : "stream_stall";
404
577
  if (!useNonStreamingFallback && stallRetries >= STALL_RETRIES_BEFORE_NON_STREAMING) {
405
578
  useNonStreamingFallback = true;
406
579
  diag("non_streaming_fallback_enabled", {
407
580
  stallRetries,
408
581
  provider: options.provider,
409
582
  model: options.model,
410
- cause: malformed ? "malformed_stream" : "stream_stall"
583
+ cause
411
584
  });
412
585
  }
413
586
  const delayMs = Math.min(STALL_DELAY_MS * 2 ** (stallRetries - 1), 8e3);
414
587
  diag("retry", {
415
- reason: malformed ? "malformed_stream" : "stream_stall",
588
+ reason: cause,
416
589
  attempt: stallRetries,
417
590
  maxAttempts: MAX_STALL_RETRIES,
418
591
  delayMs,
@@ -567,117 +740,26 @@ async function* agentLoop(messages, options) {
567
740
  toolCalls.push(tc);
568
741
  }
569
742
  }
570
- const eventStream = new import_ai.EventStream();
571
- const executions = toolCalls.map(async (toolCall) => {
572
- const startTime = Date.now();
573
- eventStream.push({
574
- type: "tool_call_start",
575
- toolCallId: toolCall.id,
576
- name: toolCall.name,
577
- args: toolCall.args
578
- });
579
- let resultContent;
580
- let details;
581
- let isError = false;
582
- const tool = toolMap.get(toolCall.name);
583
- if (!tool) {
584
- resultContent = `Unknown tool: ${toolCall.name}`;
585
- isError = true;
586
- } else {
587
- try {
588
- const parsed = tool.parameters.parse(toolCall.args);
589
- const ctx = {
590
- signal: options.signal ?? AbortSignal.timeout(3e5),
591
- toolCallId: toolCall.id,
592
- onUpdate: (update) => {
593
- eventStream.push({
594
- type: "tool_call_update",
595
- toolCallId: toolCall.id,
596
- update
597
- });
598
- }
599
- };
600
- const raw = await tool.execute(parsed, ctx);
601
- const normalized = normalizeToolResult(raw);
602
- resultContent = normalized.content;
603
- details = normalized.details;
604
- } catch (err) {
605
- isError = true;
606
- resultContent = err instanceof Error ? err.message : String(err);
607
- }
608
- }
609
- const durationMs = Date.now() - startTime;
610
- eventStream.push({
611
- type: "tool_call_end",
612
- toolCallId: toolCall.id,
613
- result: toolResultPreview(resultContent),
614
- details,
615
- isError,
616
- durationMs
617
- });
618
- return { toolCallId: toolCall.id, content: resultContent, isError };
619
- });
620
- const abortHandler = () => eventStream.abort(new Error("aborted"));
621
- options.signal?.addEventListener("abort", abortHandler, { once: true });
622
- let toolResultsFinalized = false;
623
- Promise.all(executions).then((results) => {
624
- if (toolResultsFinalized) return;
625
- const resultsMap = new Map(results.map((r) => [r.toolCallId, r]));
626
- for (const tc of toolCalls) {
627
- const r = resultsMap.get(tc.id);
628
- toolResults.push({
629
- type: "tool_result",
630
- toolCallId: tc.id,
631
- content: r.content,
632
- isError: r.isError || void 0
633
- });
634
- }
635
- eventStream.close();
636
- }).catch((err) => eventStream.abort(err instanceof Error ? err : new Error(String(err))));
637
- let toolsAborted = false;
638
- try {
639
- for await (const event of eventStream) {
640
- yield event;
641
- }
642
- } catch (err) {
643
- if (isAbortError(err) || options.signal?.aborted) {
644
- toolsAborted = true;
645
- } else {
646
- throw err;
647
- }
648
- } finally {
649
- options.signal?.removeEventListener("abort", abortHandler);
650
- toolResultsFinalized = true;
651
- const resolvedIds = new Set(toolResults.map((r) => r.toolCallId));
652
- for (const tc of toolCalls) {
653
- if (!resolvedIds.has(tc.id)) {
654
- toolResults.push({
655
- type: "tool_result",
656
- toolCallId: tc.id,
657
- content: "Tool execution was aborted.",
658
- isError: true
659
- });
660
- }
661
- }
662
- if (options.maxToolResultChars) {
663
- const HARD_MAX = 4e5;
664
- const max = Math.min(options.maxToolResultChars, HARD_MAX);
665
- for (const tr of toolResults) {
666
- if (typeof tr.content === "string" && tr.content.length > max) {
667
- const headChars = Math.floor(max * 0.7);
668
- const tailChars = max - headChars;
669
- const head = tr.content.slice(0, headChars);
670
- const tail = tr.content.slice(-tailChars);
671
- const omitted = tr.content.length - headChars - tailChars;
672
- tr.content = head + `
673
-
674
- [... ${omitted} characters omitted ...]
675
-
676
- ` + tail;
677
- }
678
- }
679
- }
680
- messages.push({ role: "tool", content: toolResults });
743
+ let fatalToolArgumentError = null;
744
+ const markFatalToolArgumentError = (error) => {
745
+ fatalToolArgumentError = error;
746
+ };
747
+ const executionOptions = {
748
+ signal: options.signal,
749
+ maxToolResultChars: options.maxToolResultChars,
750
+ toolMap,
751
+ invalidToolArgumentCounts,
752
+ markFatalToolArgumentError
753
+ };
754
+ const hasSequentialToolCall = toolCalls.some(
755
+ (toolCall) => toolMap.get(toolCall.name)?.executionMode === "sequential"
756
+ );
757
+ const executionResult = hasSequentialToolCall ? yield* executeToolCallsSequential(toolCalls, toolResults, executionOptions) : yield* executeToolCallsParallel(toolCalls, toolResults, executionOptions);
758
+ messages.push({ role: "tool", content: executionResult.toolResults });
759
+ const toolsAborted = executionResult.aborted;
760
+ if (fatalToolArgumentError) {
761
+ yield { type: "error", error: fatalToolArgumentError };
762
+ break;
681
763
  }
682
764
  if (toolsAborted) break;
683
765
  if (options.getSteeringMessages) {
@@ -711,6 +793,197 @@ async function* agentLoop(messages, options) {
711
793
  totalUsage: { ...totalUsage }
712
794
  };
713
795
  }
796
+ function pushToolEvent(eventStream, state, event) {
797
+ if (!state.finalized) eventStream.push(event);
798
+ }
799
+ async function executeSingleToolCall(toolCall, options, pushEvent) {
800
+ const startTime = Date.now();
801
+ pushEvent({
802
+ type: "tool_call_start",
803
+ toolCallId: toolCall.id,
804
+ name: toolCall.name,
805
+ args: toolCall.args
806
+ });
807
+ let resultContent;
808
+ let details;
809
+ let isError = false;
810
+ const tool = options.toolMap.get(toolCall.name);
811
+ if (!tool) {
812
+ resultContent = `Unknown tool: ${toolCall.name}`;
813
+ isError = true;
814
+ } else {
815
+ try {
816
+ const parsed = tool.parameters.parse(toolCall.args);
817
+ const ctx = {
818
+ signal: options.signal ?? AbortSignal.timeout(3e5),
819
+ toolCallId: toolCall.id,
820
+ onUpdate: (update) => {
821
+ pushEvent({
822
+ type: "tool_call_update",
823
+ toolCallId: toolCall.id,
824
+ update
825
+ });
826
+ }
827
+ };
828
+ const raw = await tool.execute(parsed, ctx);
829
+ const normalized = normalizeToolResult(raw);
830
+ resultContent = normalized.content;
831
+ details = normalized.details;
832
+ for (const key of options.invalidToolArgumentCounts.keys()) {
833
+ if (key.startsWith(`${toolCall.name}:`)) options.invalidToolArgumentCounts.delete(key);
834
+ }
835
+ } catch (err) {
836
+ isError = true;
837
+ if (err instanceof import_zod.ZodError) {
838
+ const prettyError = (0, import_zod.prettifyError)(err);
839
+ const failureKey = `${toolCall.name}:${prettyError}`;
840
+ const failureCount = (options.invalidToolArgumentCounts.get(failureKey) ?? 0) + 1;
841
+ options.invalidToolArgumentCounts.set(failureKey, failureCount);
842
+ resultContent = `Invalid arguments for tool \`${toolCall.name}\`:
843
+ ` + prettyError + "\nRe-issue the call with each field as the correct type.";
844
+ if (failureCount >= 3) {
845
+ options.markFatalToolArgumentError(
846
+ new Error(
847
+ `The model repeatedly issued invalid arguments for tool \`${toolCall.name}\`. This is usually an upstream model/tool-calling bug. Your conversation is preserved; send another message or switch models to continue.`
848
+ )
849
+ );
850
+ }
851
+ } else {
852
+ resultContent = err instanceof Error ? err.message : String(err);
853
+ }
854
+ }
855
+ }
856
+ const durationMs = Date.now() - startTime;
857
+ pushEvent({
858
+ type: "tool_call_end",
859
+ toolCallId: toolCall.id,
860
+ result: toolResultPreview(resultContent),
861
+ details,
862
+ isError,
863
+ durationMs
864
+ });
865
+ return { toolCallId: toolCall.id, content: resultContent, isError };
866
+ }
867
+ async function* executeToolCallsSequential(toolCalls, initialToolResults, options) {
868
+ const eventStream = new import_ai.EventStream();
869
+ const state = { finalized: false };
870
+ const resultsById = /* @__PURE__ */ new Map();
871
+ const abortHandler = () => eventStream.abort(new Error("aborted"));
872
+ options.signal?.addEventListener("abort", abortHandler, { once: true });
873
+ void (async () => {
874
+ try {
875
+ for (const toolCall of toolCalls) {
876
+ if (options.signal?.aborted) break;
877
+ const record = await executeSingleToolCall(
878
+ toolCall,
879
+ options,
880
+ (event) => pushToolEvent(eventStream, state, event)
881
+ );
882
+ resultsById.set(record.toolCallId, record);
883
+ }
884
+ if (!state.finalized) eventStream.close();
885
+ } catch (err) {
886
+ if (!state.finalized) eventStream.abort(err instanceof Error ? err : new Error(String(err)));
887
+ }
888
+ })();
889
+ let aborted = false;
890
+ try {
891
+ for await (const event of eventStream) {
892
+ yield event;
893
+ }
894
+ } catch (err) {
895
+ if (isAbortError(err) || options.signal?.aborted) {
896
+ aborted = true;
897
+ } else {
898
+ throw err;
899
+ }
900
+ } finally {
901
+ options.signal?.removeEventListener("abort", abortHandler);
902
+ state.finalized = true;
903
+ }
904
+ const toolResults = buildToolResults(initialToolResults, toolCalls, resultsById);
905
+ capToolResults(toolResults, options.maxToolResultChars);
906
+ return { toolResults, aborted };
907
+ }
908
+ async function* executeToolCallsParallel(toolCalls, initialToolResults, options) {
909
+ const eventStream = new import_ai.EventStream();
910
+ const state = { finalized: false };
911
+ const resultsById = /* @__PURE__ */ new Map();
912
+ const abortHandler = () => eventStream.abort(new Error("aborted"));
913
+ options.signal?.addEventListener("abort", abortHandler, { once: true });
914
+ Promise.all(
915
+ toolCalls.map(async (toolCall) => {
916
+ const record = await executeSingleToolCall(
917
+ toolCall,
918
+ options,
919
+ (event) => pushToolEvent(eventStream, state, event)
920
+ );
921
+ resultsById.set(record.toolCallId, record);
922
+ })
923
+ ).then(() => {
924
+ if (!state.finalized) eventStream.close();
925
+ }).catch((err) => {
926
+ if (!state.finalized) eventStream.abort(err instanceof Error ? err : new Error(String(err)));
927
+ });
928
+ let aborted = false;
929
+ try {
930
+ for await (const event of eventStream) {
931
+ yield event;
932
+ }
933
+ } catch (err) {
934
+ if (isAbortError(err) || options.signal?.aborted) {
935
+ aborted = true;
936
+ } else {
937
+ throw err;
938
+ }
939
+ } finally {
940
+ options.signal?.removeEventListener("abort", abortHandler);
941
+ state.finalized = true;
942
+ }
943
+ const toolResults = buildToolResults(initialToolResults, toolCalls, resultsById);
944
+ capToolResults(toolResults, options.maxToolResultChars);
945
+ return { toolResults, aborted };
946
+ }
947
+ function buildToolResults(initialToolResults, toolCalls, resultsById) {
948
+ const toolResults = [...initialToolResults];
949
+ for (const toolCall of toolCalls) {
950
+ const result = resultsById.get(toolCall.id);
951
+ if (result) {
952
+ toolResults.push({
953
+ type: "tool_result",
954
+ toolCallId: toolCall.id,
955
+ content: result.content,
956
+ isError: result.isError || void 0
957
+ });
958
+ } else {
959
+ toolResults.push({
960
+ type: "tool_result",
961
+ toolCallId: toolCall.id,
962
+ content: "Tool execution was aborted.",
963
+ isError: true
964
+ });
965
+ }
966
+ }
967
+ return toolResults;
968
+ }
969
+ function capToolResults(toolResults, maxToolResultChars) {
970
+ if (!maxToolResultChars) return;
971
+ const hardMax = 4e5;
972
+ const max = Math.min(maxToolResultChars, hardMax);
973
+ for (const toolResult of toolResults) {
974
+ if (typeof toolResult.content !== "string" || toolResult.content.length <= max) continue;
975
+ const headChars = Math.floor(max * 0.7);
976
+ const tailChars = max - headChars;
977
+ const head = toolResult.content.slice(0, headChars);
978
+ const tail = toolResult.content.slice(-tailChars);
979
+ const omitted = toolResult.content.length - headChars - tailChars;
980
+ toolResult.content = head + `
981
+
982
+ [... ${omitted} characters omitted ...]
983
+
984
+ ` + tail;
985
+ }
986
+ }
714
987
  function normalizeToolResult(raw) {
715
988
  return typeof raw === "string" ? { content: raw } : raw;
716
989
  }
@@ -718,6 +991,44 @@ function toolResultPreview(content) {
718
991
  if (typeof content === "string") return content;
719
992
  return content.map((block) => block.type === "text" ? block.text : `[image ${block.mediaType}]`).join("\n");
720
993
  }
994
+ function truncateToolResultText(text, maxChars) {
995
+ if (text.length <= maxChars) return text;
996
+ const tailChars = Math.min(Math.floor(maxChars * 0.3), 2e4);
997
+ const headChars = Math.max(maxChars - tailChars, 0);
998
+ const omitted = text.length - headChars - tailChars;
999
+ return `${text.slice(0, headChars)}
1000
+
1001
+ [... ${omitted} characters omitted after context overflow ...]
1002
+
1003
+ ${text.slice(-tailChars)}`;
1004
+ }
1005
+ function truncateOversizedToolResults(messages, maxChars) {
1006
+ if (maxChars <= 0) return false;
1007
+ let changed = false;
1008
+ for (const msg of messages) {
1009
+ if (msg.role !== "tool" || !Array.isArray(msg.content)) continue;
1010
+ const results = msg.content;
1011
+ for (const result of results) {
1012
+ if (typeof result.content === "string") {
1013
+ const truncated = truncateToolResultText(result.content, maxChars);
1014
+ if (truncated !== result.content) {
1015
+ result.content = truncated;
1016
+ changed = true;
1017
+ }
1018
+ } else {
1019
+ for (const block of result.content) {
1020
+ if (block.type !== "text") continue;
1021
+ const truncated = truncateToolResultText(block.text, maxChars);
1022
+ if (truncated !== block.text) {
1023
+ block.text = truncated;
1024
+ changed = true;
1025
+ }
1026
+ }
1027
+ }
1028
+ }
1029
+ }
1030
+ return changed;
1031
+ }
721
1032
  function extractToolCalls(content) {
722
1033
  if (typeof content === "string") return [];
723
1034
  return content.filter((part) => part.type === "tool_call");