@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.js CHANGED
@@ -2,6 +2,7 @@
2
2
  import { EventStream as EventStream2 } from "@prestyj/ai";
3
3
 
4
4
  // src/agent-loop.ts
5
+ import { ZodError, prettifyError } from "zod";
5
6
  import {
6
7
  stream,
7
8
  EventStream
@@ -26,6 +27,44 @@ function isContextOverflow(err) {
26
27
  const msg = err.message.toLowerCase();
27
28
  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");
28
29
  }
30
+ function parseOverflowNumber(value) {
31
+ return Number(value.replace(/[,_\s]/g, ""));
32
+ }
33
+ function extractContextOverflowDetails(err) {
34
+ if (!(err instanceof Error)) return {};
35
+ const text = err.message;
36
+ const patterns = [
37
+ // Anthropic/OpenAI-compatible: "203456 tokens > 200000 maximum"
38
+ {
39
+ regex: /([\d,_.\s]+)\s*tokens?\s*>\s*([\d,_.\s]+)\s*(?:maximum|max|limit)?/i,
40
+ tokensGroup: 1,
41
+ limitGroup: 2
42
+ },
43
+ // OpenAI: "maximum context length is 128000 tokens ... resulted in 130000 tokens"
44
+ {
45
+ regex: /maximum context length is\s*([\d,_.\s]+)\s*tokens?[\s\S]*?resulted in\s*([\d,_.\s]+)\s*tokens?/i,
46
+ tokensGroup: 2,
47
+ limitGroup: 1
48
+ },
49
+ // Generic: "130000 input tokens exceeds 128000 token limit"
50
+ {
51
+ 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,
52
+ tokensGroup: 1,
53
+ limitGroup: 2
54
+ }
55
+ ];
56
+ for (const pattern of patterns) {
57
+ const match = text.match(pattern.regex);
58
+ if (!match) continue;
59
+ const observedTokens = parseOverflowNumber(match[pattern.tokensGroup] ?? "");
60
+ const observedLimit = parseOverflowNumber(match[pattern.limitGroup] ?? "");
61
+ return {
62
+ ...Number.isFinite(observedTokens) && observedTokens > 0 ? { observedTokens } : {},
63
+ ...Number.isFinite(observedLimit) && observedLimit > 0 ? { observedLimit } : {}
64
+ };
65
+ }
66
+ return {};
67
+ }
29
68
  function isBillingError(err) {
30
69
  if (!(err instanceof Error)) return false;
31
70
  const msg = err.message.toLowerCase();
@@ -41,12 +80,17 @@ function classifyOverload(err) {
41
80
  if (!(err instanceof Error)) return null;
42
81
  if (isBillingError(err)) return null;
43
82
  const msg = err.message.toLowerCase();
44
- if (msg.includes("rate_limit") || msg.includes("rate limit") || msg.includes("too many requests") || msg.includes("429")) {
83
+ const errorWithStatus = err;
84
+ const statusCode = typeof errorWithStatus.statusCode === "number" ? errorWithStatus.statusCode : void 0;
85
+ if (statusCode === 429 || msg.includes("rate_limit") || msg.includes("rate limit") || msg.includes("too many requests") || msg.includes("429")) {
45
86
  return "rate_limit";
46
87
  }
47
- if (msg.includes("overloaded") || msg.includes("529")) {
88
+ if (statusCode === 529 || msg.includes("overloaded") || msg.includes("529")) {
48
89
  return "overloaded";
49
90
  }
91
+ 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")) {
92
+ return "provider_error";
93
+ }
50
94
  return null;
51
95
  }
52
96
  function isMalformedStream(err) {
@@ -57,6 +101,46 @@ function isMalformedStream(err) {
57
101
  const msg = err.message;
58
102
  return /\bin JSON at position \d+/i.test(msg);
59
103
  }
104
+ function isTransportFailure(err) {
105
+ const codes = /* @__PURE__ */ new Set([
106
+ "ECONNRESET",
107
+ "ECONNREFUSED",
108
+ "ECONNABORTED",
109
+ "ETIMEDOUT",
110
+ "EPIPE",
111
+ "EHOSTUNREACH",
112
+ "ENETUNREACH",
113
+ "ENOTFOUND",
114
+ "UND_ERR_SOCKET",
115
+ "UND_ERR_CONNECT_TIMEOUT",
116
+ "UND_ERR_HEADERS_TIMEOUT",
117
+ "UND_ERR_BODY_TIMEOUT",
118
+ "UND_ERR_RESPONSE_STATUS_CODE",
119
+ "UND_ERR_REQ_CONTENT_LENGTH_MISMATCH",
120
+ "UND_ERR_RES_CONTENT_LENGTH_MISMATCH"
121
+ ]);
122
+ const messages = [
123
+ /^terminated$/i,
124
+ /\bother side closed\b/i,
125
+ /\bsocket hang up\b/i,
126
+ /\bfetch failed\b/i,
127
+ /\bbody timeout error\b/i,
128
+ /\bsse stream disconnected\b/i,
129
+ /\bfailed to reconnect sse stream\b/i
130
+ ];
131
+ const seen = /* @__PURE__ */ new Set();
132
+ let cur = err;
133
+ while (cur && typeof cur === "object" && !seen.has(cur)) {
134
+ seen.add(cur);
135
+ const e = cur;
136
+ if (typeof e.code === "string" && codes.has(e.code)) return true;
137
+ if (typeof e.message === "string") {
138
+ for (const re of messages) if (re.test(e.message)) return true;
139
+ }
140
+ cur = e.cause;
141
+ }
142
+ return false;
143
+ }
60
144
  function abortableSleep(ms, signal) {
61
145
  if (signal?.aborted) {
62
146
  return Promise.reject(new DOMException("Aborted", "AbortError"));
@@ -87,6 +171,8 @@ async function* agentLoop(messages, options) {
87
171
  let emptyResponseRetries = 0;
88
172
  let stallRetries = 0;
89
173
  let overflowCompactionAttempts = 0;
174
+ let toolResultTruncationAttempted = false;
175
+ const invalidToolArgumentCounts = /* @__PURE__ */ new Map();
90
176
  let useNonStreamingFallback = false;
91
177
  const MAX_OVERLOAD_RETRIES = 10;
92
178
  const MAX_EMPTY_RESPONSE_RETRIES = 2;
@@ -103,6 +189,8 @@ async function* agentLoop(messages, options) {
103
189
  const STREAM_THINKING_IDLE_TIMEOUT_MS = 3e5;
104
190
  const STREAM_THINKING_HARD_TIMEOUT_MS = 6e5;
105
191
  const NON_STREAMING_HARD_TIMEOUT_MS = 3e5;
192
+ const MAX_TOOLCALL_DELTA_CHARS = 1e6;
193
+ const MAX_TOOLCALL_DELTA_EVENTS = 2e4;
106
194
  try {
107
195
  while (turn < maxTurns) {
108
196
  options.signal?.throwIfAborted();
@@ -158,6 +246,9 @@ async function* agentLoop(messages, options) {
158
246
  let streamCallStart = Date.now();
159
247
  const eventTypeCounts = {};
160
248
  let lastEventType = "";
249
+ let toolcallDeltaChars = 0;
250
+ let toolcallDeltaCount = 0;
251
+ let runawayDetected = null;
161
252
  let lastYieldEndTime = Date.now();
162
253
  let maxConsumerLagMs = 0;
163
254
  const forwardAbort = () => streamController.abort();
@@ -208,9 +299,12 @@ async function* agentLoop(messages, options) {
208
299
  signal: streamController.signal,
209
300
  accountId: options.accountId,
210
301
  cacheRetention: options.cacheRetention,
302
+ promptCacheKey: options.promptCacheKey,
303
+ serviceTier: options.serviceTier,
211
304
  supportsImages: options.supportsImages,
212
305
  compaction: options.compaction,
213
306
  clearToolUses: options.clearToolUses,
307
+ userAgent: options.userAgent,
214
308
  // Flip to non-streaming fallback after repeated stream stalls.
215
309
  ...useNonStreamingFallback ? { streaming: false } : {}
216
310
  });
@@ -221,11 +315,14 @@ async function* agentLoop(messages, options) {
221
315
  hasReceivedEvent = false;
222
316
  lastEventTime = Date.now();
223
317
  streamCallStart = Date.now();
318
+ lastYieldEndTime = Date.now();
224
319
  resetIdleTimer();
225
320
  for await (const event of result) {
226
321
  const pullTime = Date.now();
227
322
  const consumerLag = pullTime - lastYieldEndTime;
228
- if (consumerLag > maxConsumerLagMs) maxConsumerLagMs = consumerLag;
323
+ if (streamEventCount > 0 && consumerLag > maxConsumerLagMs) {
324
+ maxConsumerLagMs = consumerLag;
325
+ }
229
326
  streamEventCount++;
230
327
  eventTypeCounts[event.type] = (eventTypeCounts[event.type] ?? 0) + 1;
231
328
  lastEventType = event.type;
@@ -284,9 +381,25 @@ async function* agentLoop(messages, options) {
284
381
  data: event.data
285
382
  };
286
383
  } else if (event.type === "toolcall_delta") {
384
+ const chunkChars = event.argsJson?.length ?? 0;
385
+ toolcallDeltaChars += chunkChars;
386
+ toolcallDeltaCount++;
387
+ if (!runawayDetected && (toolcallDeltaChars > MAX_TOOLCALL_DELTA_CHARS || toolcallDeltaCount > MAX_TOOLCALL_DELTA_EVENTS)) {
388
+ runawayDetected = {
389
+ kind: toolcallDeltaChars > MAX_TOOLCALL_DELTA_CHARS ? "chars" : "events",
390
+ chars: toolcallDeltaChars,
391
+ events: toolcallDeltaCount
392
+ };
393
+ diag("runaway_toolcall_detected", {
394
+ ...runawayDetected,
395
+ provider: options.provider,
396
+ model: options.model
397
+ });
398
+ streamController.abort();
399
+ }
287
400
  yield {
288
401
  type: "toolcall_delta",
289
- chars: event.argsJson?.length ?? 0
402
+ chars: chunkChars
290
403
  };
291
404
  }
292
405
  lastYieldEndTime = Date.now();
@@ -311,12 +424,44 @@ async function* agentLoop(messages, options) {
311
424
  model: options.model
312
425
  });
313
426
  if (isContextOverflow(err)) {
427
+ const overflowDetails = extractContextOverflowDetails(err);
428
+ diag("context_overflow_detected", {
429
+ ...overflowDetails,
430
+ error: errMsg.slice(0, 500),
431
+ messages: messages.length
432
+ });
433
+ const overflowToolResultMaxChars = Math.min(
434
+ options.maxToolResultChars ?? 1e5,
435
+ 1e5
436
+ );
437
+ if (!toolResultTruncationAttempted) {
438
+ toolResultTruncationAttempted = true;
439
+ const truncated = truncateOversizedToolResults(messages, overflowToolResultMaxChars);
440
+ diag("overflow_tool_result_truncation", {
441
+ truncated,
442
+ maxChars: overflowToolResultMaxChars
443
+ });
444
+ if (truncated) {
445
+ yield {
446
+ type: "retry",
447
+ reason: "overflow_compact",
448
+ attempt: overflowCompactionAttempts + 1,
449
+ maxAttempts: MAX_OVERFLOW_COMPACTIONS,
450
+ delayMs: 0,
451
+ ...overflowDetails,
452
+ silent: true
453
+ };
454
+ turn--;
455
+ continue;
456
+ }
457
+ }
314
458
  if (options.transformContext && overflowCompactionAttempts < MAX_OVERFLOW_COMPACTIONS) {
315
459
  overflowCompactionAttempts++;
316
460
  diag("overflow_compact_start", {
317
461
  attempt: overflowCompactionAttempts,
318
462
  maxAttempts: MAX_OVERFLOW_COMPACTIONS,
319
- messages: messages.length
463
+ messages: messages.length,
464
+ ...overflowDetails
320
465
  });
321
466
  try {
322
467
  const compacted = await options.transformContext(messages, { force: true });
@@ -325,14 +470,16 @@ async function* agentLoop(messages, options) {
325
470
  messages.push(...compacted);
326
471
  diag("overflow_compact_success", {
327
472
  attempt: overflowCompactionAttempts,
328
- messages: messages.length
473
+ messages: messages.length,
474
+ ...overflowDetails
329
475
  });
330
476
  yield {
331
477
  type: "retry",
332
478
  reason: "overflow_compact",
333
479
  attempt: overflowCompactionAttempts,
334
480
  maxAttempts: MAX_OVERFLOW_COMPACTIONS,
335
- delayMs: 0
481
+ delayMs: 0,
482
+ ...overflowDetails
336
483
  };
337
484
  turn--;
338
485
  continue;
@@ -340,11 +487,13 @@ async function* agentLoop(messages, options) {
340
487
  diag("overflow_compact_noop", {
341
488
  attempt: overflowCompactionAttempts,
342
489
  before: messages.length,
343
- after: compacted.length
490
+ after: compacted.length,
491
+ ...overflowDetails
344
492
  });
345
493
  } catch (compactErr) {
346
494
  diag("overflow_compact_failed", {
347
- error: compactErr instanceof Error ? compactErr.message : String(compactErr)
495
+ error: compactErr instanceof Error ? compactErr.message : String(compactErr),
496
+ ...overflowDetails
348
497
  });
349
498
  }
350
499
  }
@@ -375,22 +524,39 @@ async function* agentLoop(messages, options) {
375
524
  turn--;
376
525
  continue;
377
526
  }
527
+ if (runawayDetected) {
528
+ diag("runaway_toolcall_aborted", {
529
+ ...runawayDetected,
530
+ provider: options.provider,
531
+ model: options.model
532
+ });
533
+ const detail = runawayDetected.kind === "chars" ? `${(runawayDetected.chars / 1024).toFixed(0)} KB of tool-call arguments` : `${runawayDetected.events} tool-call delta events`;
534
+ yield {
535
+ type: "error",
536
+ error: new Error(
537
+ `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.`
538
+ )
539
+ };
540
+ break;
541
+ }
378
542
  const malformed = isMalformedStream(err);
379
- const transportFailure = (idleTimedOut || malformed) && !options.signal?.aborted;
543
+ const socketDrop = isTransportFailure(err);
544
+ const transportFailure = (idleTimedOut || malformed || socketDrop) && !options.signal?.aborted;
380
545
  if (transportFailure && stallRetries < MAX_STALL_RETRIES) {
381
546
  stallRetries++;
547
+ const cause = malformed ? "malformed_stream" : socketDrop ? "socket_drop" : "stream_stall";
382
548
  if (!useNonStreamingFallback && stallRetries >= STALL_RETRIES_BEFORE_NON_STREAMING) {
383
549
  useNonStreamingFallback = true;
384
550
  diag("non_streaming_fallback_enabled", {
385
551
  stallRetries,
386
552
  provider: options.provider,
387
553
  model: options.model,
388
- cause: malformed ? "malformed_stream" : "stream_stall"
554
+ cause
389
555
  });
390
556
  }
391
557
  const delayMs = Math.min(STALL_DELAY_MS * 2 ** (stallRetries - 1), 8e3);
392
558
  diag("retry", {
393
- reason: malformed ? "malformed_stream" : "stream_stall",
559
+ reason: cause,
394
560
  attempt: stallRetries,
395
561
  maxAttempts: MAX_STALL_RETRIES,
396
562
  delayMs,
@@ -545,117 +711,26 @@ async function* agentLoop(messages, options) {
545
711
  toolCalls.push(tc);
546
712
  }
547
713
  }
548
- const eventStream = new EventStream();
549
- const executions = toolCalls.map(async (toolCall) => {
550
- const startTime = Date.now();
551
- eventStream.push({
552
- type: "tool_call_start",
553
- toolCallId: toolCall.id,
554
- name: toolCall.name,
555
- args: toolCall.args
556
- });
557
- let resultContent;
558
- let details;
559
- let isError = false;
560
- const tool = toolMap.get(toolCall.name);
561
- if (!tool) {
562
- resultContent = `Unknown tool: ${toolCall.name}`;
563
- isError = true;
564
- } else {
565
- try {
566
- const parsed = tool.parameters.parse(toolCall.args);
567
- const ctx = {
568
- signal: options.signal ?? AbortSignal.timeout(3e5),
569
- toolCallId: toolCall.id,
570
- onUpdate: (update) => {
571
- eventStream.push({
572
- type: "tool_call_update",
573
- toolCallId: toolCall.id,
574
- update
575
- });
576
- }
577
- };
578
- const raw = await tool.execute(parsed, ctx);
579
- const normalized = normalizeToolResult(raw);
580
- resultContent = normalized.content;
581
- details = normalized.details;
582
- } catch (err) {
583
- isError = true;
584
- resultContent = err instanceof Error ? err.message : String(err);
585
- }
586
- }
587
- const durationMs = Date.now() - startTime;
588
- eventStream.push({
589
- type: "tool_call_end",
590
- toolCallId: toolCall.id,
591
- result: toolResultPreview(resultContent),
592
- details,
593
- isError,
594
- durationMs
595
- });
596
- return { toolCallId: toolCall.id, content: resultContent, isError };
597
- });
598
- const abortHandler = () => eventStream.abort(new Error("aborted"));
599
- options.signal?.addEventListener("abort", abortHandler, { once: true });
600
- let toolResultsFinalized = false;
601
- Promise.all(executions).then((results) => {
602
- if (toolResultsFinalized) return;
603
- const resultsMap = new Map(results.map((r) => [r.toolCallId, r]));
604
- for (const tc of toolCalls) {
605
- const r = resultsMap.get(tc.id);
606
- toolResults.push({
607
- type: "tool_result",
608
- toolCallId: tc.id,
609
- content: r.content,
610
- isError: r.isError || void 0
611
- });
612
- }
613
- eventStream.close();
614
- }).catch((err) => eventStream.abort(err instanceof Error ? err : new Error(String(err))));
615
- let toolsAborted = false;
616
- try {
617
- for await (const event of eventStream) {
618
- yield event;
619
- }
620
- } catch (err) {
621
- if (isAbortError(err) || options.signal?.aborted) {
622
- toolsAborted = true;
623
- } else {
624
- throw err;
625
- }
626
- } finally {
627
- options.signal?.removeEventListener("abort", abortHandler);
628
- toolResultsFinalized = true;
629
- const resolvedIds = new Set(toolResults.map((r) => r.toolCallId));
630
- for (const tc of toolCalls) {
631
- if (!resolvedIds.has(tc.id)) {
632
- toolResults.push({
633
- type: "tool_result",
634
- toolCallId: tc.id,
635
- content: "Tool execution was aborted.",
636
- isError: true
637
- });
638
- }
639
- }
640
- if (options.maxToolResultChars) {
641
- const HARD_MAX = 4e5;
642
- const max = Math.min(options.maxToolResultChars, HARD_MAX);
643
- for (const tr of toolResults) {
644
- if (typeof tr.content === "string" && tr.content.length > max) {
645
- const headChars = Math.floor(max * 0.7);
646
- const tailChars = max - headChars;
647
- const head = tr.content.slice(0, headChars);
648
- const tail = tr.content.slice(-tailChars);
649
- const omitted = tr.content.length - headChars - tailChars;
650
- tr.content = head + `
651
-
652
- [... ${omitted} characters omitted ...]
653
-
654
- ` + tail;
655
- }
656
- }
657
- }
658
- messages.push({ role: "tool", content: toolResults });
714
+ let fatalToolArgumentError = null;
715
+ const markFatalToolArgumentError = (error) => {
716
+ fatalToolArgumentError = error;
717
+ };
718
+ const executionOptions = {
719
+ signal: options.signal,
720
+ maxToolResultChars: options.maxToolResultChars,
721
+ toolMap,
722
+ invalidToolArgumentCounts,
723
+ markFatalToolArgumentError
724
+ };
725
+ const hasSequentialToolCall = toolCalls.some(
726
+ (toolCall) => toolMap.get(toolCall.name)?.executionMode === "sequential"
727
+ );
728
+ const executionResult = hasSequentialToolCall ? yield* executeToolCallsSequential(toolCalls, toolResults, executionOptions) : yield* executeToolCallsParallel(toolCalls, toolResults, executionOptions);
729
+ messages.push({ role: "tool", content: executionResult.toolResults });
730
+ const toolsAborted = executionResult.aborted;
731
+ if (fatalToolArgumentError) {
732
+ yield { type: "error", error: fatalToolArgumentError };
733
+ break;
659
734
  }
660
735
  if (toolsAborted) break;
661
736
  if (options.getSteeringMessages) {
@@ -689,6 +764,197 @@ async function* agentLoop(messages, options) {
689
764
  totalUsage: { ...totalUsage }
690
765
  };
691
766
  }
767
+ function pushToolEvent(eventStream, state, event) {
768
+ if (!state.finalized) eventStream.push(event);
769
+ }
770
+ async function executeSingleToolCall(toolCall, options, pushEvent) {
771
+ const startTime = Date.now();
772
+ pushEvent({
773
+ type: "tool_call_start",
774
+ toolCallId: toolCall.id,
775
+ name: toolCall.name,
776
+ args: toolCall.args
777
+ });
778
+ let resultContent;
779
+ let details;
780
+ let isError = false;
781
+ const tool = options.toolMap.get(toolCall.name);
782
+ if (!tool) {
783
+ resultContent = `Unknown tool: ${toolCall.name}`;
784
+ isError = true;
785
+ } else {
786
+ try {
787
+ const parsed = tool.parameters.parse(toolCall.args);
788
+ const ctx = {
789
+ signal: options.signal ?? AbortSignal.timeout(3e5),
790
+ toolCallId: toolCall.id,
791
+ onUpdate: (update) => {
792
+ pushEvent({
793
+ type: "tool_call_update",
794
+ toolCallId: toolCall.id,
795
+ update
796
+ });
797
+ }
798
+ };
799
+ const raw = await tool.execute(parsed, ctx);
800
+ const normalized = normalizeToolResult(raw);
801
+ resultContent = normalized.content;
802
+ details = normalized.details;
803
+ for (const key of options.invalidToolArgumentCounts.keys()) {
804
+ if (key.startsWith(`${toolCall.name}:`)) options.invalidToolArgumentCounts.delete(key);
805
+ }
806
+ } catch (err) {
807
+ isError = true;
808
+ if (err instanceof ZodError) {
809
+ const prettyError = prettifyError(err);
810
+ const failureKey = `${toolCall.name}:${prettyError}`;
811
+ const failureCount = (options.invalidToolArgumentCounts.get(failureKey) ?? 0) + 1;
812
+ options.invalidToolArgumentCounts.set(failureKey, failureCount);
813
+ resultContent = `Invalid arguments for tool \`${toolCall.name}\`:
814
+ ` + prettyError + "\nRe-issue the call with each field as the correct type.";
815
+ if (failureCount >= 3) {
816
+ options.markFatalToolArgumentError(
817
+ new Error(
818
+ `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.`
819
+ )
820
+ );
821
+ }
822
+ } else {
823
+ resultContent = err instanceof Error ? err.message : String(err);
824
+ }
825
+ }
826
+ }
827
+ const durationMs = Date.now() - startTime;
828
+ pushEvent({
829
+ type: "tool_call_end",
830
+ toolCallId: toolCall.id,
831
+ result: toolResultPreview(resultContent),
832
+ details,
833
+ isError,
834
+ durationMs
835
+ });
836
+ return { toolCallId: toolCall.id, content: resultContent, isError };
837
+ }
838
+ async function* executeToolCallsSequential(toolCalls, initialToolResults, options) {
839
+ const eventStream = new EventStream();
840
+ const state = { finalized: false };
841
+ const resultsById = /* @__PURE__ */ new Map();
842
+ const abortHandler = () => eventStream.abort(new Error("aborted"));
843
+ options.signal?.addEventListener("abort", abortHandler, { once: true });
844
+ void (async () => {
845
+ try {
846
+ for (const toolCall of toolCalls) {
847
+ if (options.signal?.aborted) break;
848
+ const record = await executeSingleToolCall(
849
+ toolCall,
850
+ options,
851
+ (event) => pushToolEvent(eventStream, state, event)
852
+ );
853
+ resultsById.set(record.toolCallId, record);
854
+ }
855
+ if (!state.finalized) eventStream.close();
856
+ } catch (err) {
857
+ if (!state.finalized) eventStream.abort(err instanceof Error ? err : new Error(String(err)));
858
+ }
859
+ })();
860
+ let aborted = false;
861
+ try {
862
+ for await (const event of eventStream) {
863
+ yield event;
864
+ }
865
+ } catch (err) {
866
+ if (isAbortError(err) || options.signal?.aborted) {
867
+ aborted = true;
868
+ } else {
869
+ throw err;
870
+ }
871
+ } finally {
872
+ options.signal?.removeEventListener("abort", abortHandler);
873
+ state.finalized = true;
874
+ }
875
+ const toolResults = buildToolResults(initialToolResults, toolCalls, resultsById);
876
+ capToolResults(toolResults, options.maxToolResultChars);
877
+ return { toolResults, aborted };
878
+ }
879
+ async function* executeToolCallsParallel(toolCalls, initialToolResults, options) {
880
+ const eventStream = new EventStream();
881
+ const state = { finalized: false };
882
+ const resultsById = /* @__PURE__ */ new Map();
883
+ const abortHandler = () => eventStream.abort(new Error("aborted"));
884
+ options.signal?.addEventListener("abort", abortHandler, { once: true });
885
+ Promise.all(
886
+ toolCalls.map(async (toolCall) => {
887
+ const record = await executeSingleToolCall(
888
+ toolCall,
889
+ options,
890
+ (event) => pushToolEvent(eventStream, state, event)
891
+ );
892
+ resultsById.set(record.toolCallId, record);
893
+ })
894
+ ).then(() => {
895
+ if (!state.finalized) eventStream.close();
896
+ }).catch((err) => {
897
+ if (!state.finalized) eventStream.abort(err instanceof Error ? err : new Error(String(err)));
898
+ });
899
+ let aborted = false;
900
+ try {
901
+ for await (const event of eventStream) {
902
+ yield event;
903
+ }
904
+ } catch (err) {
905
+ if (isAbortError(err) || options.signal?.aborted) {
906
+ aborted = true;
907
+ } else {
908
+ throw err;
909
+ }
910
+ } finally {
911
+ options.signal?.removeEventListener("abort", abortHandler);
912
+ state.finalized = true;
913
+ }
914
+ const toolResults = buildToolResults(initialToolResults, toolCalls, resultsById);
915
+ capToolResults(toolResults, options.maxToolResultChars);
916
+ return { toolResults, aborted };
917
+ }
918
+ function buildToolResults(initialToolResults, toolCalls, resultsById) {
919
+ const toolResults = [...initialToolResults];
920
+ for (const toolCall of toolCalls) {
921
+ const result = resultsById.get(toolCall.id);
922
+ if (result) {
923
+ toolResults.push({
924
+ type: "tool_result",
925
+ toolCallId: toolCall.id,
926
+ content: result.content,
927
+ isError: result.isError || void 0
928
+ });
929
+ } else {
930
+ toolResults.push({
931
+ type: "tool_result",
932
+ toolCallId: toolCall.id,
933
+ content: "Tool execution was aborted.",
934
+ isError: true
935
+ });
936
+ }
937
+ }
938
+ return toolResults;
939
+ }
940
+ function capToolResults(toolResults, maxToolResultChars) {
941
+ if (!maxToolResultChars) return;
942
+ const hardMax = 4e5;
943
+ const max = Math.min(maxToolResultChars, hardMax);
944
+ for (const toolResult of toolResults) {
945
+ if (typeof toolResult.content !== "string" || toolResult.content.length <= max) continue;
946
+ const headChars = Math.floor(max * 0.7);
947
+ const tailChars = max - headChars;
948
+ const head = toolResult.content.slice(0, headChars);
949
+ const tail = toolResult.content.slice(-tailChars);
950
+ const omitted = toolResult.content.length - headChars - tailChars;
951
+ toolResult.content = head + `
952
+
953
+ [... ${omitted} characters omitted ...]
954
+
955
+ ` + tail;
956
+ }
957
+ }
692
958
  function normalizeToolResult(raw) {
693
959
  return typeof raw === "string" ? { content: raw } : raw;
694
960
  }
@@ -696,6 +962,44 @@ function toolResultPreview(content) {
696
962
  if (typeof content === "string") return content;
697
963
  return content.map((block) => block.type === "text" ? block.text : `[image ${block.mediaType}]`).join("\n");
698
964
  }
965
+ function truncateToolResultText(text, maxChars) {
966
+ if (text.length <= maxChars) return text;
967
+ const tailChars = Math.min(Math.floor(maxChars * 0.3), 2e4);
968
+ const headChars = Math.max(maxChars - tailChars, 0);
969
+ const omitted = text.length - headChars - tailChars;
970
+ return `${text.slice(0, headChars)}
971
+
972
+ [... ${omitted} characters omitted after context overflow ...]
973
+
974
+ ${text.slice(-tailChars)}`;
975
+ }
976
+ function truncateOversizedToolResults(messages, maxChars) {
977
+ if (maxChars <= 0) return false;
978
+ let changed = false;
979
+ for (const msg of messages) {
980
+ if (msg.role !== "tool" || !Array.isArray(msg.content)) continue;
981
+ const results = msg.content;
982
+ for (const result of results) {
983
+ if (typeof result.content === "string") {
984
+ const truncated = truncateToolResultText(result.content, maxChars);
985
+ if (truncated !== result.content) {
986
+ result.content = truncated;
987
+ changed = true;
988
+ }
989
+ } else {
990
+ for (const block of result.content) {
991
+ if (block.type !== "text") continue;
992
+ const truncated = truncateToolResultText(block.text, maxChars);
993
+ if (truncated !== block.text) {
994
+ block.text = truncated;
995
+ changed = true;
996
+ }
997
+ }
998
+ }
999
+ }
1000
+ }
1001
+ return changed;
1002
+ }
699
1003
  function extractToolCalls(content) {
700
1004
  if (typeof content === "string") return [];
701
1005
  return content.filter((part) => part.type === "tool_call");