@prestyj/agent 4.3.164 → 4.3.201

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();
@@ -70,12 +109,17 @@ function classifyOverload(err) {
70
109
  if (!(err instanceof Error)) return null;
71
110
  if (isBillingError(err)) return null;
72
111
  const msg = err.message.toLowerCase();
73
- if (msg.includes("rate_limit") || msg.includes("rate limit") || msg.includes("too many requests") || msg.includes("429")) {
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")) {
74
115
  return "rate_limit";
75
116
  }
76
- if (msg.includes("overloaded") || msg.includes("529")) {
117
+ if (statusCode === 529 || msg.includes("overloaded") || msg.includes("529")) {
77
118
  return "overloaded";
78
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
+ }
79
123
  return null;
80
124
  }
81
125
  function isMalformedStream(err) {
@@ -86,6 +130,46 @@ function isMalformedStream(err) {
86
130
  const msg = err.message;
87
131
  return /\bin JSON at position \d+/i.test(msg);
88
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
+ }
89
173
  function abortableSleep(ms, signal) {
90
174
  if (signal?.aborted) {
91
175
  return Promise.reject(new DOMException("Aborted", "AbortError"));
@@ -116,6 +200,8 @@ async function* agentLoop(messages, options) {
116
200
  let emptyResponseRetries = 0;
117
201
  let stallRetries = 0;
118
202
  let overflowCompactionAttempts = 0;
203
+ let toolResultTruncationAttempted = false;
204
+ const invalidToolArgumentCounts = /* @__PURE__ */ new Map();
119
205
  let useNonStreamingFallback = false;
120
206
  const MAX_OVERLOAD_RETRIES = 10;
121
207
  const MAX_EMPTY_RESPONSE_RETRIES = 2;
@@ -132,6 +218,8 @@ async function* agentLoop(messages, options) {
132
218
  const STREAM_THINKING_IDLE_TIMEOUT_MS = 3e5;
133
219
  const STREAM_THINKING_HARD_TIMEOUT_MS = 6e5;
134
220
  const NON_STREAMING_HARD_TIMEOUT_MS = 3e5;
221
+ const MAX_TOOLCALL_DELTA_CHARS = 1e6;
222
+ const MAX_TOOLCALL_DELTA_EVENTS = 2e4;
135
223
  try {
136
224
  while (turn < maxTurns) {
137
225
  options.signal?.throwIfAborted();
@@ -187,6 +275,9 @@ async function* agentLoop(messages, options) {
187
275
  let streamCallStart = Date.now();
188
276
  const eventTypeCounts = {};
189
277
  let lastEventType = "";
278
+ let toolcallDeltaChars = 0;
279
+ let toolcallDeltaCount = 0;
280
+ let runawayDetected = null;
190
281
  let lastYieldEndTime = Date.now();
191
282
  let maxConsumerLagMs = 0;
192
283
  const forwardAbort = () => streamController.abort();
@@ -237,9 +328,12 @@ async function* agentLoop(messages, options) {
237
328
  signal: streamController.signal,
238
329
  accountId: options.accountId,
239
330
  cacheRetention: options.cacheRetention,
331
+ promptCacheKey: options.promptCacheKey,
332
+ serviceTier: options.serviceTier,
240
333
  supportsImages: options.supportsImages,
241
334
  compaction: options.compaction,
242
335
  clearToolUses: options.clearToolUses,
336
+ userAgent: options.userAgent,
243
337
  // Flip to non-streaming fallback after repeated stream stalls.
244
338
  ...useNonStreamingFallback ? { streaming: false } : {}
245
339
  });
@@ -250,11 +344,14 @@ async function* agentLoop(messages, options) {
250
344
  hasReceivedEvent = false;
251
345
  lastEventTime = Date.now();
252
346
  streamCallStart = Date.now();
347
+ lastYieldEndTime = Date.now();
253
348
  resetIdleTimer();
254
349
  for await (const event of result) {
255
350
  const pullTime = Date.now();
256
351
  const consumerLag = pullTime - lastYieldEndTime;
257
- if (consumerLag > maxConsumerLagMs) maxConsumerLagMs = consumerLag;
352
+ if (streamEventCount > 0 && consumerLag > maxConsumerLagMs) {
353
+ maxConsumerLagMs = consumerLag;
354
+ }
258
355
  streamEventCount++;
259
356
  eventTypeCounts[event.type] = (eventTypeCounts[event.type] ?? 0) + 1;
260
357
  lastEventType = event.type;
@@ -313,9 +410,25 @@ async function* agentLoop(messages, options) {
313
410
  data: event.data
314
411
  };
315
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
+ }
316
429
  yield {
317
430
  type: "toolcall_delta",
318
- chars: event.argsJson?.length ?? 0
431
+ chars: chunkChars
319
432
  };
320
433
  }
321
434
  lastYieldEndTime = Date.now();
@@ -340,12 +453,44 @@ async function* agentLoop(messages, options) {
340
453
  model: options.model
341
454
  });
342
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
+ }
343
487
  if (options.transformContext && overflowCompactionAttempts < MAX_OVERFLOW_COMPACTIONS) {
344
488
  overflowCompactionAttempts++;
345
489
  diag("overflow_compact_start", {
346
490
  attempt: overflowCompactionAttempts,
347
491
  maxAttempts: MAX_OVERFLOW_COMPACTIONS,
348
- messages: messages.length
492
+ messages: messages.length,
493
+ ...overflowDetails
349
494
  });
350
495
  try {
351
496
  const compacted = await options.transformContext(messages, { force: true });
@@ -354,14 +499,16 @@ async function* agentLoop(messages, options) {
354
499
  messages.push(...compacted);
355
500
  diag("overflow_compact_success", {
356
501
  attempt: overflowCompactionAttempts,
357
- messages: messages.length
502
+ messages: messages.length,
503
+ ...overflowDetails
358
504
  });
359
505
  yield {
360
506
  type: "retry",
361
507
  reason: "overflow_compact",
362
508
  attempt: overflowCompactionAttempts,
363
509
  maxAttempts: MAX_OVERFLOW_COMPACTIONS,
364
- delayMs: 0
510
+ delayMs: 0,
511
+ ...overflowDetails
365
512
  };
366
513
  turn--;
367
514
  continue;
@@ -369,11 +516,13 @@ async function* agentLoop(messages, options) {
369
516
  diag("overflow_compact_noop", {
370
517
  attempt: overflowCompactionAttempts,
371
518
  before: messages.length,
372
- after: compacted.length
519
+ after: compacted.length,
520
+ ...overflowDetails
373
521
  });
374
522
  } catch (compactErr) {
375
523
  diag("overflow_compact_failed", {
376
- error: compactErr instanceof Error ? compactErr.message : String(compactErr)
524
+ error: compactErr instanceof Error ? compactErr.message : String(compactErr),
525
+ ...overflowDetails
377
526
  });
378
527
  }
379
528
  }
@@ -404,22 +553,39 @@ async function* agentLoop(messages, options) {
404
553
  turn--;
405
554
  continue;
406
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
+ }
407
571
  const malformed = isMalformedStream(err);
408
- const transportFailure = (idleTimedOut || malformed) && !options.signal?.aborted;
572
+ const socketDrop = isTransportFailure(err);
573
+ const transportFailure = (idleTimedOut || malformed || socketDrop) && !options.signal?.aborted;
409
574
  if (transportFailure && stallRetries < MAX_STALL_RETRIES) {
410
575
  stallRetries++;
576
+ const cause = malformed ? "malformed_stream" : socketDrop ? "socket_drop" : "stream_stall";
411
577
  if (!useNonStreamingFallback && stallRetries >= STALL_RETRIES_BEFORE_NON_STREAMING) {
412
578
  useNonStreamingFallback = true;
413
579
  diag("non_streaming_fallback_enabled", {
414
580
  stallRetries,
415
581
  provider: options.provider,
416
582
  model: options.model,
417
- cause: malformed ? "malformed_stream" : "stream_stall"
583
+ cause
418
584
  });
419
585
  }
420
586
  const delayMs = Math.min(STALL_DELAY_MS * 2 ** (stallRetries - 1), 8e3);
421
587
  diag("retry", {
422
- reason: malformed ? "malformed_stream" : "stream_stall",
588
+ reason: cause,
423
589
  attempt: stallRetries,
424
590
  maxAttempts: MAX_STALL_RETRIES,
425
591
  delayMs,
@@ -574,117 +740,26 @@ async function* agentLoop(messages, options) {
574
740
  toolCalls.push(tc);
575
741
  }
576
742
  }
577
- const eventStream = new import_ai.EventStream();
578
- const executions = toolCalls.map(async (toolCall) => {
579
- const startTime = Date.now();
580
- eventStream.push({
581
- type: "tool_call_start",
582
- toolCallId: toolCall.id,
583
- name: toolCall.name,
584
- args: toolCall.args
585
- });
586
- let resultContent;
587
- let details;
588
- let isError = false;
589
- const tool = toolMap.get(toolCall.name);
590
- if (!tool) {
591
- resultContent = `Unknown tool: ${toolCall.name}`;
592
- isError = true;
593
- } else {
594
- try {
595
- const parsed = tool.parameters.parse(toolCall.args);
596
- const ctx = {
597
- signal: options.signal ?? AbortSignal.timeout(3e5),
598
- toolCallId: toolCall.id,
599
- onUpdate: (update) => {
600
- eventStream.push({
601
- type: "tool_call_update",
602
- toolCallId: toolCall.id,
603
- update
604
- });
605
- }
606
- };
607
- const raw = await tool.execute(parsed, ctx);
608
- const normalized = normalizeToolResult(raw);
609
- resultContent = normalized.content;
610
- details = normalized.details;
611
- } catch (err) {
612
- isError = true;
613
- resultContent = err instanceof Error ? err.message : String(err);
614
- }
615
- }
616
- const durationMs = Date.now() - startTime;
617
- eventStream.push({
618
- type: "tool_call_end",
619
- toolCallId: toolCall.id,
620
- result: toolResultPreview(resultContent),
621
- details,
622
- isError,
623
- durationMs
624
- });
625
- return { toolCallId: toolCall.id, content: resultContent, isError };
626
- });
627
- const abortHandler = () => eventStream.abort(new Error("aborted"));
628
- options.signal?.addEventListener("abort", abortHandler, { once: true });
629
- let toolResultsFinalized = false;
630
- Promise.all(executions).then((results) => {
631
- if (toolResultsFinalized) return;
632
- const resultsMap = new Map(results.map((r) => [r.toolCallId, r]));
633
- for (const tc of toolCalls) {
634
- const r = resultsMap.get(tc.id);
635
- toolResults.push({
636
- type: "tool_result",
637
- toolCallId: tc.id,
638
- content: r.content,
639
- isError: r.isError || void 0
640
- });
641
- }
642
- eventStream.close();
643
- }).catch((err) => eventStream.abort(err instanceof Error ? err : new Error(String(err))));
644
- let toolsAborted = false;
645
- try {
646
- for await (const event of eventStream) {
647
- yield event;
648
- }
649
- } catch (err) {
650
- if (isAbortError(err) || options.signal?.aborted) {
651
- toolsAborted = true;
652
- } else {
653
- throw err;
654
- }
655
- } finally {
656
- options.signal?.removeEventListener("abort", abortHandler);
657
- toolResultsFinalized = true;
658
- const resolvedIds = new Set(toolResults.map((r) => r.toolCallId));
659
- for (const tc of toolCalls) {
660
- if (!resolvedIds.has(tc.id)) {
661
- toolResults.push({
662
- type: "tool_result",
663
- toolCallId: tc.id,
664
- content: "Tool execution was aborted.",
665
- isError: true
666
- });
667
- }
668
- }
669
- if (options.maxToolResultChars) {
670
- const HARD_MAX = 4e5;
671
- const max = Math.min(options.maxToolResultChars, HARD_MAX);
672
- for (const tr of toolResults) {
673
- if (typeof tr.content === "string" && tr.content.length > max) {
674
- const headChars = Math.floor(max * 0.7);
675
- const tailChars = max - headChars;
676
- const head = tr.content.slice(0, headChars);
677
- const tail = tr.content.slice(-tailChars);
678
- const omitted = tr.content.length - headChars - tailChars;
679
- tr.content = head + `
680
-
681
- [... ${omitted} characters omitted ...]
682
-
683
- ` + tail;
684
- }
685
- }
686
- }
687
- 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;
688
763
  }
689
764
  if (toolsAborted) break;
690
765
  if (options.getSteeringMessages) {
@@ -718,6 +793,197 @@ async function* agentLoop(messages, options) {
718
793
  totalUsage: { ...totalUsage }
719
794
  };
720
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
+ }
721
987
  function normalizeToolResult(raw) {
722
988
  return typeof raw === "string" ? { content: raw } : raw;
723
989
  }
@@ -725,6 +991,44 @@ function toolResultPreview(content) {
725
991
  if (typeof content === "string") return content;
726
992
  return content.map((block) => block.type === "text" ? block.text : `[image ${block.mediaType}]`).join("\n");
727
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
+ }
728
1032
  function extractToolCalls(content) {
729
1033
  if (typeof content === "string") return [];
730
1034
  return content.filter((part) => part.type === "tool_call");