@jaypie/llm 1.2.33 → 1.2.35

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.
@@ -4817,6 +4817,78 @@ class RetryPolicy {
4817
4817
  // Export a default policy instance
4818
4818
  const defaultRetryPolicy = new RetryPolicy();
4819
4819
 
4820
+ //
4821
+ //
4822
+ // Helpers
4823
+ //
4824
+ /**
4825
+ * Compare an unhandled rejection reason against errors the retry loop has
4826
+ * already caught. Reference equality first, then fall back to message + name
4827
+ * — providers sometimes surface twin rejections as fresh Error instances
4828
+ * rebuilt from the same upstream failure.
4829
+ */
4830
+ function matchesCaughtError(reason, caught) {
4831
+ if (caught.has(reason))
4832
+ return true;
4833
+ if (!(reason instanceof Error))
4834
+ return false;
4835
+ for (const handled of caught) {
4836
+ if (handled instanceof Error &&
4837
+ handled.name === reason.name &&
4838
+ handled.message === reason.message) {
4839
+ return true;
4840
+ }
4841
+ }
4842
+ return false;
4843
+ }
4844
+ //
4845
+ //
4846
+ // Main
4847
+ //
4848
+ /**
4849
+ * Create a guard that suppresses unhandled rejections firing as siblings of
4850
+ * an error the retry loop has already caught. Provider SDKs occasionally
4851
+ * surface a single upstream failure as twin rejections — the retry layer
4852
+ * accepts responsibility for the first; this guard prevents the second from
4853
+ * crashing the host while the retry is in flight.
4854
+ *
4855
+ * The guard also continues to suppress transient socket teardown errors
4856
+ * (e.g. undici `TypeError: terminated`) emitted between attempts.
4857
+ */
4858
+ function createStaleRejectionGuard() {
4859
+ const log = getLogger$5();
4860
+ const caughtErrors = new Set();
4861
+ let listener;
4862
+ return {
4863
+ install() {
4864
+ if (listener)
4865
+ return;
4866
+ listener = (reason, promise) => {
4867
+ if (isTransientNetworkError(reason)) {
4868
+ promise?.catch?.(() => { });
4869
+ log.trace("Suppressed stale socket error during retry");
4870
+ return;
4871
+ }
4872
+ if (matchesCaughtError(reason, caughtErrors)) {
4873
+ promise?.catch?.(() => { });
4874
+ log.trace("Suppressed sibling rejection of already-handled error");
4875
+ }
4876
+ };
4877
+ process.on("unhandledRejection", listener);
4878
+ },
4879
+ remove() {
4880
+ if (listener) {
4881
+ process.removeListener("unhandledRejection", listener);
4882
+ listener = undefined;
4883
+ }
4884
+ caughtErrors.clear();
4885
+ },
4886
+ recordCaught(error) {
4887
+ caughtErrors.add(error);
4888
+ },
4889
+ };
4890
+ }
4891
+
4820
4892
  //
4821
4893
  //
4822
4894
  // Main
@@ -4845,27 +4917,11 @@ class RetryExecutor {
4845
4917
  async execute(operation, options) {
4846
4918
  const log = getLogger$5();
4847
4919
  let attempt = 0;
4848
- // Persistent guard against stale socket errors (TypeError: terminated).
4849
- // Installed after the first abort and kept alive through subsequent attempts
4850
- // so that asynchronous undici socket teardown errors that fire between
4851
- // sleep completion and the next operation's await are caught.
4852
- let staleGuard;
4853
- const installGuard = () => {
4854
- if (staleGuard)
4855
- return;
4856
- staleGuard = (reason) => {
4857
- if (isTransientNetworkError(reason)) {
4858
- log.trace("Suppressed stale socket error during retry");
4859
- }
4860
- };
4861
- process.on("unhandledRejection", staleGuard);
4862
- };
4863
- const removeGuard = () => {
4864
- if (staleGuard) {
4865
- process.removeListener("unhandledRejection", staleGuard);
4866
- staleGuard = undefined;
4867
- }
4868
- };
4920
+ // Guard against stale rejections firing on a subsequent microtask after
4921
+ // the retry layer has already caught the originating error: undici socket
4922
+ // teardown (TypeError: terminated) and twin upstream-SDK rejections
4923
+ // (e.g. issue #336 OpenRouter SyntaxError siblings).
4924
+ const guard = createStaleRejectionGuard();
4869
4925
  try {
4870
4926
  while (true) {
4871
4927
  const controller = new AbortController();
@@ -4877,12 +4933,9 @@ class RetryExecutor {
4877
4933
  return result;
4878
4934
  }
4879
4935
  catch (error) {
4880
- // Abort the previous request to kill lingering socket callbacks
4881
4936
  controller.abort("retry");
4882
- // Install the guard immediately after abort — stale socket errors
4883
- // can fire on any subsequent microtask boundary (during hook calls,
4884
- // sleep, or the next operation attempt)
4885
- installGuard();
4937
+ guard.recordCaught(error);
4938
+ guard.install();
4886
4939
  // Check if we've exhausted retries
4887
4940
  if (!this.policy.shouldRetry(attempt)) {
4888
4941
  log.error(`API call failed after ${this.policy.maxRetries} retries`);
@@ -4928,7 +4981,7 @@ class RetryExecutor {
4928
4981
  }
4929
4982
  }
4930
4983
  finally {
4931
- removeGuard();
4984
+ guard.remove();
4932
4985
  }
4933
4986
  }
4934
4987
  }
@@ -5231,7 +5284,7 @@ class OperateLoop {
5231
5284
  };
5232
5285
  const toolResultFormatted = this.adapter.formatToolResult(toolCall, errorResult);
5233
5286
  state.responseBuilder.appendToHistory(toolResultFormatted);
5234
- log.error(`Error executing function call ${toolCall.name}`);
5287
+ log.warn(`Error executing function call ${toolCall.name}`);
5235
5288
  log.var({ error });
5236
5289
  // Track consecutive errors and stop if threshold reached
5237
5290
  state.consecutiveToolErrors++;
@@ -5538,25 +5591,10 @@ class StreamLoop {
5538
5591
  // Retry loop for connection-level failures
5539
5592
  let attempt = 0;
5540
5593
  let chunksYielded = false;
5541
- // Persistent guard against stale socket errors (TypeError: terminated).
5542
- // Installed after the first abort and kept alive through subsequent attempts.
5543
- let staleGuard;
5544
- const installGuard = () => {
5545
- if (staleGuard)
5546
- return;
5547
- staleGuard = (reason) => {
5548
- if (isTransientNetworkError(reason)) {
5549
- log.trace("Suppressed stale socket error during retry");
5550
- }
5551
- };
5552
- process.on("unhandledRejection", staleGuard);
5553
- };
5554
- const removeGuard = () => {
5555
- if (staleGuard) {
5556
- process.removeListener("unhandledRejection", staleGuard);
5557
- staleGuard = undefined;
5558
- }
5559
- };
5594
+ // Guard against stale rejections firing after the stream loop has already
5595
+ // caught the originating error: undici socket teardown and twin
5596
+ // upstream-SDK rejections (issue #336).
5597
+ const guard = createStaleRejectionGuard();
5560
5598
  try {
5561
5599
  while (true) {
5562
5600
  const controller = new AbortController();
@@ -5597,10 +5635,9 @@ class StreamLoop {
5597
5635
  break;
5598
5636
  }
5599
5637
  catch (error) {
5600
- // Abort the previous request to kill lingering socket callbacks
5601
5638
  controller.abort("retry");
5602
- // Install the guard immediately after abort
5603
- installGuard();
5639
+ guard.recordCaught(error);
5640
+ guard.install();
5604
5641
  // If chunks were already yielded, we can't transparently retry
5605
5642
  if (chunksYielded) {
5606
5643
  const errorMessage = error instanceof Error ? error.message : String(error);
@@ -5633,7 +5670,7 @@ class StreamLoop {
5633
5670
  }
5634
5671
  }
5635
5672
  finally {
5636
- removeGuard();
5673
+ guard.remove();
5637
5674
  }
5638
5675
  // Execute afterEachModelResponse hook
5639
5676
  await this.hookRunnerInstance.runAfterModelResponse(context.hooks, {
@@ -5742,7 +5779,7 @@ class StreamLoop {
5742
5779
  call_id: toolCall.callId,
5743
5780
  name: toolCall.name,
5744
5781
  });
5745
- log.error(`Error executing function call ${toolCall.name}`);
5782
+ log.warn(`Error executing function call ${toolCall.name}`);
5746
5783
  log.var({ error });
5747
5784
  // Track consecutive errors and stop if threshold reached
5748
5785
  state.consecutiveToolErrors++;