@jaypie/llm 1.2.33 → 1.2.34
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/cjs/index.cjs +87 -50
- package/dist/cjs/index.cjs.map +1 -1
- package/dist/cjs/operate/retry/createStaleRejectionGuard.d.ts +19 -0
- package/dist/esm/index.js +87 -50
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/operate/retry/createStaleRejectionGuard.d.ts +19 -0
- package/package.json +1 -1
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export interface StaleRejectionGuard {
|
|
2
|
+
/** Install the unhandledRejection listener (idempotent) */
|
|
3
|
+
install(): void;
|
|
4
|
+
/** Remove the listener and forget recorded errors */
|
|
5
|
+
remove(): void;
|
|
6
|
+
/** Record an error the retry loop has caught and is handling */
|
|
7
|
+
recordCaught(error: unknown): void;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Create a guard that suppresses unhandled rejections firing as siblings of
|
|
11
|
+
* an error the retry loop has already caught. Provider SDKs occasionally
|
|
12
|
+
* surface a single upstream failure as twin rejections — the retry layer
|
|
13
|
+
* accepts responsibility for the first; this guard prevents the second from
|
|
14
|
+
* crashing the host while the retry is in flight.
|
|
15
|
+
*
|
|
16
|
+
* The guard also continues to suppress transient socket teardown errors
|
|
17
|
+
* (e.g. undici `TypeError: terminated`) emitted between attempts.
|
|
18
|
+
*/
|
|
19
|
+
export declare function createStaleRejectionGuard(): StaleRejectionGuard;
|
package/dist/esm/index.js
CHANGED
|
@@ -4815,6 +4815,78 @@ class RetryPolicy {
|
|
|
4815
4815
|
// Export a default policy instance
|
|
4816
4816
|
const defaultRetryPolicy = new RetryPolicy();
|
|
4817
4817
|
|
|
4818
|
+
//
|
|
4819
|
+
//
|
|
4820
|
+
// Helpers
|
|
4821
|
+
//
|
|
4822
|
+
/**
|
|
4823
|
+
* Compare an unhandled rejection reason against errors the retry loop has
|
|
4824
|
+
* already caught. Reference equality first, then fall back to message + name
|
|
4825
|
+
* — providers sometimes surface twin rejections as fresh Error instances
|
|
4826
|
+
* rebuilt from the same upstream failure.
|
|
4827
|
+
*/
|
|
4828
|
+
function matchesCaughtError(reason, caught) {
|
|
4829
|
+
if (caught.has(reason))
|
|
4830
|
+
return true;
|
|
4831
|
+
if (!(reason instanceof Error))
|
|
4832
|
+
return false;
|
|
4833
|
+
for (const handled of caught) {
|
|
4834
|
+
if (handled instanceof Error &&
|
|
4835
|
+
handled.name === reason.name &&
|
|
4836
|
+
handled.message === reason.message) {
|
|
4837
|
+
return true;
|
|
4838
|
+
}
|
|
4839
|
+
}
|
|
4840
|
+
return false;
|
|
4841
|
+
}
|
|
4842
|
+
//
|
|
4843
|
+
//
|
|
4844
|
+
// Main
|
|
4845
|
+
//
|
|
4846
|
+
/**
|
|
4847
|
+
* Create a guard that suppresses unhandled rejections firing as siblings of
|
|
4848
|
+
* an error the retry loop has already caught. Provider SDKs occasionally
|
|
4849
|
+
* surface a single upstream failure as twin rejections — the retry layer
|
|
4850
|
+
* accepts responsibility for the first; this guard prevents the second from
|
|
4851
|
+
* crashing the host while the retry is in flight.
|
|
4852
|
+
*
|
|
4853
|
+
* The guard also continues to suppress transient socket teardown errors
|
|
4854
|
+
* (e.g. undici `TypeError: terminated`) emitted between attempts.
|
|
4855
|
+
*/
|
|
4856
|
+
function createStaleRejectionGuard() {
|
|
4857
|
+
const log = getLogger$5();
|
|
4858
|
+
const caughtErrors = new Set();
|
|
4859
|
+
let listener;
|
|
4860
|
+
return {
|
|
4861
|
+
install() {
|
|
4862
|
+
if (listener)
|
|
4863
|
+
return;
|
|
4864
|
+
listener = (reason, promise) => {
|
|
4865
|
+
if (isTransientNetworkError(reason)) {
|
|
4866
|
+
promise?.catch?.(() => { });
|
|
4867
|
+
log.trace("Suppressed stale socket error during retry");
|
|
4868
|
+
return;
|
|
4869
|
+
}
|
|
4870
|
+
if (matchesCaughtError(reason, caughtErrors)) {
|
|
4871
|
+
promise?.catch?.(() => { });
|
|
4872
|
+
log.trace("Suppressed sibling rejection of already-handled error");
|
|
4873
|
+
}
|
|
4874
|
+
};
|
|
4875
|
+
process.on("unhandledRejection", listener);
|
|
4876
|
+
},
|
|
4877
|
+
remove() {
|
|
4878
|
+
if (listener) {
|
|
4879
|
+
process.removeListener("unhandledRejection", listener);
|
|
4880
|
+
listener = undefined;
|
|
4881
|
+
}
|
|
4882
|
+
caughtErrors.clear();
|
|
4883
|
+
},
|
|
4884
|
+
recordCaught(error) {
|
|
4885
|
+
caughtErrors.add(error);
|
|
4886
|
+
},
|
|
4887
|
+
};
|
|
4888
|
+
}
|
|
4889
|
+
|
|
4818
4890
|
//
|
|
4819
4891
|
//
|
|
4820
4892
|
// Main
|
|
@@ -4843,27 +4915,11 @@ class RetryExecutor {
|
|
|
4843
4915
|
async execute(operation, options) {
|
|
4844
4916
|
const log = getLogger$5();
|
|
4845
4917
|
let attempt = 0;
|
|
4846
|
-
//
|
|
4847
|
-
//
|
|
4848
|
-
//
|
|
4849
|
-
//
|
|
4850
|
-
|
|
4851
|
-
const installGuard = () => {
|
|
4852
|
-
if (staleGuard)
|
|
4853
|
-
return;
|
|
4854
|
-
staleGuard = (reason) => {
|
|
4855
|
-
if (isTransientNetworkError(reason)) {
|
|
4856
|
-
log.trace("Suppressed stale socket error during retry");
|
|
4857
|
-
}
|
|
4858
|
-
};
|
|
4859
|
-
process.on("unhandledRejection", staleGuard);
|
|
4860
|
-
};
|
|
4861
|
-
const removeGuard = () => {
|
|
4862
|
-
if (staleGuard) {
|
|
4863
|
-
process.removeListener("unhandledRejection", staleGuard);
|
|
4864
|
-
staleGuard = undefined;
|
|
4865
|
-
}
|
|
4866
|
-
};
|
|
4918
|
+
// Guard against stale rejections firing on a subsequent microtask after
|
|
4919
|
+
// the retry layer has already caught the originating error: undici socket
|
|
4920
|
+
// teardown (TypeError: terminated) and twin upstream-SDK rejections
|
|
4921
|
+
// (e.g. issue #336 — OpenRouter SyntaxError siblings).
|
|
4922
|
+
const guard = createStaleRejectionGuard();
|
|
4867
4923
|
try {
|
|
4868
4924
|
while (true) {
|
|
4869
4925
|
const controller = new AbortController();
|
|
@@ -4875,12 +4931,9 @@ class RetryExecutor {
|
|
|
4875
4931
|
return result;
|
|
4876
4932
|
}
|
|
4877
4933
|
catch (error) {
|
|
4878
|
-
// Abort the previous request to kill lingering socket callbacks
|
|
4879
4934
|
controller.abort("retry");
|
|
4880
|
-
|
|
4881
|
-
|
|
4882
|
-
// sleep, or the next operation attempt)
|
|
4883
|
-
installGuard();
|
|
4935
|
+
guard.recordCaught(error);
|
|
4936
|
+
guard.install();
|
|
4884
4937
|
// Check if we've exhausted retries
|
|
4885
4938
|
if (!this.policy.shouldRetry(attempt)) {
|
|
4886
4939
|
log.error(`API call failed after ${this.policy.maxRetries} retries`);
|
|
@@ -4926,7 +4979,7 @@ class RetryExecutor {
|
|
|
4926
4979
|
}
|
|
4927
4980
|
}
|
|
4928
4981
|
finally {
|
|
4929
|
-
|
|
4982
|
+
guard.remove();
|
|
4930
4983
|
}
|
|
4931
4984
|
}
|
|
4932
4985
|
}
|
|
@@ -5536,25 +5589,10 @@ class StreamLoop {
|
|
|
5536
5589
|
// Retry loop for connection-level failures
|
|
5537
5590
|
let attempt = 0;
|
|
5538
5591
|
let chunksYielded = false;
|
|
5539
|
-
//
|
|
5540
|
-
//
|
|
5541
|
-
|
|
5542
|
-
const
|
|
5543
|
-
if (staleGuard)
|
|
5544
|
-
return;
|
|
5545
|
-
staleGuard = (reason) => {
|
|
5546
|
-
if (isTransientNetworkError(reason)) {
|
|
5547
|
-
log.trace("Suppressed stale socket error during retry");
|
|
5548
|
-
}
|
|
5549
|
-
};
|
|
5550
|
-
process.on("unhandledRejection", staleGuard);
|
|
5551
|
-
};
|
|
5552
|
-
const removeGuard = () => {
|
|
5553
|
-
if (staleGuard) {
|
|
5554
|
-
process.removeListener("unhandledRejection", staleGuard);
|
|
5555
|
-
staleGuard = undefined;
|
|
5556
|
-
}
|
|
5557
|
-
};
|
|
5592
|
+
// Guard against stale rejections firing after the stream loop has already
|
|
5593
|
+
// caught the originating error: undici socket teardown and twin
|
|
5594
|
+
// upstream-SDK rejections (issue #336).
|
|
5595
|
+
const guard = createStaleRejectionGuard();
|
|
5558
5596
|
try {
|
|
5559
5597
|
while (true) {
|
|
5560
5598
|
const controller = new AbortController();
|
|
@@ -5595,10 +5633,9 @@ class StreamLoop {
|
|
|
5595
5633
|
break;
|
|
5596
5634
|
}
|
|
5597
5635
|
catch (error) {
|
|
5598
|
-
// Abort the previous request to kill lingering socket callbacks
|
|
5599
5636
|
controller.abort("retry");
|
|
5600
|
-
|
|
5601
|
-
|
|
5637
|
+
guard.recordCaught(error);
|
|
5638
|
+
guard.install();
|
|
5602
5639
|
// If chunks were already yielded, we can't transparently retry
|
|
5603
5640
|
if (chunksYielded) {
|
|
5604
5641
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
@@ -5631,7 +5668,7 @@ class StreamLoop {
|
|
|
5631
5668
|
}
|
|
5632
5669
|
}
|
|
5633
5670
|
finally {
|
|
5634
|
-
|
|
5671
|
+
guard.remove();
|
|
5635
5672
|
}
|
|
5636
5673
|
// Execute afterEachModelResponse hook
|
|
5637
5674
|
await this.hookRunnerInstance.runAfterModelResponse(context.hooks, {
|