@jaypie/llm 1.2.14 → 1.2.15
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 +161 -129
- package/dist/cjs/index.cjs.map +1 -1
- package/dist/esm/index.js +161 -129
- package/dist/esm/index.js.map +1 -1
- package/package.json +1 -1
package/dist/esm/index.js
CHANGED
|
@@ -3955,73 +3955,91 @@ class RetryExecutor {
|
|
|
3955
3955
|
*/
|
|
3956
3956
|
async execute(operation, options) {
|
|
3957
3957
|
let attempt = 0;
|
|
3958
|
-
|
|
3959
|
-
|
|
3960
|
-
|
|
3961
|
-
|
|
3962
|
-
|
|
3963
|
-
|
|
3958
|
+
// Persistent guard against stale socket errors (TypeError: terminated).
|
|
3959
|
+
// Installed after the first abort and kept alive through subsequent attempts
|
|
3960
|
+
// so that asynchronous undici socket teardown errors that fire between
|
|
3961
|
+
// sleep completion and the next operation's await are caught.
|
|
3962
|
+
let staleGuard;
|
|
3963
|
+
const installGuard = () => {
|
|
3964
|
+
if (staleGuard)
|
|
3965
|
+
return;
|
|
3966
|
+
staleGuard = (reason) => {
|
|
3967
|
+
if (isTransientNetworkError(reason)) {
|
|
3968
|
+
log$1.trace("Suppressed stale socket error during retry");
|
|
3964
3969
|
}
|
|
3965
|
-
|
|
3970
|
+
};
|
|
3971
|
+
process.on("unhandledRejection", staleGuard);
|
|
3972
|
+
};
|
|
3973
|
+
const removeGuard = () => {
|
|
3974
|
+
if (staleGuard) {
|
|
3975
|
+
process.removeListener("unhandledRejection", staleGuard);
|
|
3976
|
+
staleGuard = undefined;
|
|
3966
3977
|
}
|
|
3967
|
-
|
|
3968
|
-
|
|
3969
|
-
|
|
3970
|
-
|
|
3971
|
-
|
|
3972
|
-
|
|
3973
|
-
|
|
3974
|
-
|
|
3975
|
-
|
|
3976
|
-
|
|
3977
|
-
providerRequest: options.context.providerRequest,
|
|
3978
|
-
error,
|
|
3979
|
-
});
|
|
3980
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
3981
|
-
throw new BadGatewayError(errorMessage);
|
|
3978
|
+
};
|
|
3979
|
+
try {
|
|
3980
|
+
while (true) {
|
|
3981
|
+
const controller = new AbortController();
|
|
3982
|
+
try {
|
|
3983
|
+
const result = await operation(controller.signal);
|
|
3984
|
+
if (attempt > 0) {
|
|
3985
|
+
log$1.debug(`API call succeeded after ${attempt} retries`);
|
|
3986
|
+
}
|
|
3987
|
+
return result;
|
|
3982
3988
|
}
|
|
3983
|
-
|
|
3984
|
-
|
|
3985
|
-
|
|
3986
|
-
|
|
3987
|
-
|
|
3989
|
+
catch (error) {
|
|
3990
|
+
// Abort the previous request to kill lingering socket callbacks
|
|
3991
|
+
controller.abort("retry");
|
|
3992
|
+
// Install the guard immediately after abort — stale socket errors
|
|
3993
|
+
// can fire on any subsequent microtask boundary (during hook calls,
|
|
3994
|
+
// sleep, or the next operation attempt)
|
|
3995
|
+
installGuard();
|
|
3996
|
+
// Check if we've exhausted retries
|
|
3997
|
+
if (!this.policy.shouldRetry(attempt)) {
|
|
3998
|
+
log$1.error(`API call failed after ${this.policy.maxRetries} retries`);
|
|
3999
|
+
log$1.var({ error });
|
|
4000
|
+
await this.hookRunner.runOnUnrecoverableError(options.hooks, {
|
|
4001
|
+
input: options.context.input,
|
|
4002
|
+
options: options.context.options,
|
|
4003
|
+
providerRequest: options.context.providerRequest,
|
|
4004
|
+
error,
|
|
4005
|
+
});
|
|
4006
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
4007
|
+
throw new BadGatewayError(errorMessage);
|
|
4008
|
+
}
|
|
4009
|
+
// Check if error is not retryable
|
|
4010
|
+
if (!this.errorClassifier.isRetryable(error)) {
|
|
4011
|
+
log$1.error("API call failed with non-retryable error");
|
|
4012
|
+
log$1.var({ error });
|
|
4013
|
+
await this.hookRunner.runOnUnrecoverableError(options.hooks, {
|
|
4014
|
+
input: options.context.input,
|
|
4015
|
+
options: options.context.options,
|
|
4016
|
+
providerRequest: options.context.providerRequest,
|
|
4017
|
+
error,
|
|
4018
|
+
});
|
|
4019
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
4020
|
+
throw new BadGatewayError(errorMessage);
|
|
4021
|
+
}
|
|
4022
|
+
// Warn if this is an unknown error type
|
|
4023
|
+
if (!this.errorClassifier.isKnownError(error)) {
|
|
4024
|
+
log$1.warn("API returned unknown error type, will retry");
|
|
4025
|
+
log$1.var({ error });
|
|
4026
|
+
}
|
|
4027
|
+
const delay = this.policy.getDelayForAttempt(attempt);
|
|
4028
|
+
log$1.warn(`API call failed. Retrying in ${delay}ms...`);
|
|
4029
|
+
await this.hookRunner.runOnRetryableError(options.hooks, {
|
|
3988
4030
|
input: options.context.input,
|
|
3989
4031
|
options: options.context.options,
|
|
3990
4032
|
providerRequest: options.context.providerRequest,
|
|
3991
4033
|
error,
|
|
3992
4034
|
});
|
|
3993
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
3994
|
-
throw new BadGatewayError(errorMessage);
|
|
3995
|
-
}
|
|
3996
|
-
// Warn if this is an unknown error type
|
|
3997
|
-
if (!this.errorClassifier.isKnownError(error)) {
|
|
3998
|
-
log$1.warn("API returned unknown error type, will retry");
|
|
3999
|
-
log$1.var({ error });
|
|
4000
|
-
}
|
|
4001
|
-
const delay = this.policy.getDelayForAttempt(attempt);
|
|
4002
|
-
log$1.warn(`API call failed. Retrying in ${delay}ms...`);
|
|
4003
|
-
await this.hookRunner.runOnRetryableError(options.hooks, {
|
|
4004
|
-
input: options.context.input,
|
|
4005
|
-
options: options.context.options,
|
|
4006
|
-
providerRequest: options.context.providerRequest,
|
|
4007
|
-
error,
|
|
4008
|
-
});
|
|
4009
|
-
// Guard against stale socket errors that fire during sleep
|
|
4010
|
-
const staleHandler = (reason) => {
|
|
4011
|
-
if (isTransientNetworkError(reason)) {
|
|
4012
|
-
log$1.trace("Suppressed stale socket error during retry sleep");
|
|
4013
|
-
}
|
|
4014
|
-
};
|
|
4015
|
-
process.on("unhandledRejection", staleHandler);
|
|
4016
|
-
try {
|
|
4017
4035
|
await sleep(delay);
|
|
4036
|
+
attempt++;
|
|
4018
4037
|
}
|
|
4019
|
-
finally {
|
|
4020
|
-
process.removeListener("unhandledRejection", staleHandler);
|
|
4021
|
-
}
|
|
4022
|
-
attempt++;
|
|
4023
4038
|
}
|
|
4024
4039
|
}
|
|
4040
|
+
finally {
|
|
4041
|
+
removeGuard();
|
|
4042
|
+
}
|
|
4025
4043
|
}
|
|
4026
4044
|
}
|
|
4027
4045
|
|
|
@@ -4595,89 +4613,103 @@ class StreamLoop {
|
|
|
4595
4613
|
// Retry loop for connection-level failures
|
|
4596
4614
|
let attempt = 0;
|
|
4597
4615
|
let chunksYielded = false;
|
|
4598
|
-
|
|
4599
|
-
|
|
4600
|
-
|
|
4601
|
-
|
|
4602
|
-
|
|
4603
|
-
|
|
4604
|
-
|
|
4605
|
-
|
|
4606
|
-
|
|
4607
|
-
|
|
4616
|
+
// Persistent guard against stale socket errors (TypeError: terminated).
|
|
4617
|
+
// Installed after the first abort and kept alive through subsequent attempts.
|
|
4618
|
+
let staleGuard;
|
|
4619
|
+
const installGuard = () => {
|
|
4620
|
+
if (staleGuard)
|
|
4621
|
+
return;
|
|
4622
|
+
staleGuard = (reason) => {
|
|
4623
|
+
if (isTransientNetworkError(reason)) {
|
|
4624
|
+
log$1.trace("Suppressed stale socket error during retry");
|
|
4625
|
+
}
|
|
4626
|
+
};
|
|
4627
|
+
process.on("unhandledRejection", staleGuard);
|
|
4628
|
+
};
|
|
4629
|
+
const removeGuard = () => {
|
|
4630
|
+
if (staleGuard) {
|
|
4631
|
+
process.removeListener("unhandledRejection", staleGuard);
|
|
4632
|
+
staleGuard = undefined;
|
|
4633
|
+
}
|
|
4634
|
+
};
|
|
4635
|
+
try {
|
|
4636
|
+
while (true) {
|
|
4637
|
+
const controller = new AbortController();
|
|
4638
|
+
try {
|
|
4639
|
+
// Execute streaming request
|
|
4640
|
+
const streamGenerator = this.adapter.executeStreamRequest(this.client, providerRequest, controller.signal);
|
|
4641
|
+
for await (const chunk of streamGenerator) {
|
|
4642
|
+
// Pass through text chunks
|
|
4643
|
+
if (chunk.type === LlmStreamChunkType.Text) {
|
|
4644
|
+
chunksYielded = true;
|
|
4645
|
+
yield chunk;
|
|
4646
|
+
}
|
|
4647
|
+
// Collect tool calls
|
|
4648
|
+
if (chunk.type === LlmStreamChunkType.ToolCall) {
|
|
4649
|
+
chunksYielded = true;
|
|
4650
|
+
collectedToolCalls.push({
|
|
4651
|
+
callId: chunk.toolCall.id,
|
|
4652
|
+
name: chunk.toolCall.name,
|
|
4653
|
+
arguments: chunk.toolCall.arguments,
|
|
4654
|
+
raw: chunk.toolCall,
|
|
4655
|
+
});
|
|
4656
|
+
yield chunk;
|
|
4657
|
+
}
|
|
4658
|
+
// Track usage from done chunk (but don't yield it yet - we'll emit our own)
|
|
4659
|
+
if (chunk.type === LlmStreamChunkType.Done && chunk.usage) {
|
|
4660
|
+
state.usageItems.push(...chunk.usage);
|
|
4661
|
+
}
|
|
4662
|
+
// Pass through error chunks
|
|
4663
|
+
if (chunk.type === LlmStreamChunkType.Error) {
|
|
4664
|
+
chunksYielded = true;
|
|
4665
|
+
yield chunk;
|
|
4666
|
+
}
|
|
4608
4667
|
}
|
|
4609
|
-
//
|
|
4610
|
-
if (
|
|
4611
|
-
|
|
4612
|
-
collectedToolCalls.push({
|
|
4613
|
-
callId: chunk.toolCall.id,
|
|
4614
|
-
name: chunk.toolCall.name,
|
|
4615
|
-
arguments: chunk.toolCall.arguments,
|
|
4616
|
-
raw: chunk.toolCall,
|
|
4617
|
-
});
|
|
4618
|
-
yield chunk;
|
|
4668
|
+
// Stream completed successfully
|
|
4669
|
+
if (attempt > 0) {
|
|
4670
|
+
log$1.debug(`Stream request succeeded after ${attempt} retries`);
|
|
4619
4671
|
}
|
|
4620
|
-
|
|
4621
|
-
|
|
4622
|
-
|
|
4672
|
+
break;
|
|
4673
|
+
}
|
|
4674
|
+
catch (error) {
|
|
4675
|
+
// Abort the previous request to kill lingering socket callbacks
|
|
4676
|
+
controller.abort("retry");
|
|
4677
|
+
// Install the guard immediately after abort
|
|
4678
|
+
installGuard();
|
|
4679
|
+
// If chunks were already yielded, we can't transparently retry
|
|
4680
|
+
if (chunksYielded) {
|
|
4681
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
4682
|
+
log$1.error("Stream failed after partial data was delivered");
|
|
4683
|
+
log$1.var({ error });
|
|
4684
|
+
yield {
|
|
4685
|
+
type: LlmStreamChunkType.Error,
|
|
4686
|
+
error: {
|
|
4687
|
+
detail: errorMessage,
|
|
4688
|
+
status: 502,
|
|
4689
|
+
title: "Stream Error",
|
|
4690
|
+
},
|
|
4691
|
+
};
|
|
4692
|
+
return { shouldContinue: false };
|
|
4623
4693
|
}
|
|
4624
|
-
//
|
|
4625
|
-
if (
|
|
4626
|
-
|
|
4627
|
-
|
|
4694
|
+
// Check if we've exhausted retries or error is not retryable
|
|
4695
|
+
if (!this.retryPolicy.shouldRetry(attempt) ||
|
|
4696
|
+
!this.adapter.isRetryableError(error)) {
|
|
4697
|
+
log$1.error(`Stream request failed after ${this.retryPolicy.maxRetries} retries`);
|
|
4698
|
+
log$1.var({ error });
|
|
4699
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
4700
|
+
throw new BadGatewayError(errorMessage);
|
|
4628
4701
|
}
|
|
4629
|
-
|
|
4630
|
-
|
|
4631
|
-
if (attempt > 0) {
|
|
4632
|
-
log$1.debug(`Stream request succeeded after ${attempt} retries`);
|
|
4633
|
-
}
|
|
4634
|
-
break;
|
|
4635
|
-
}
|
|
4636
|
-
catch (error) {
|
|
4637
|
-
// Abort the previous request to kill lingering socket callbacks
|
|
4638
|
-
controller.abort("retry");
|
|
4639
|
-
// If chunks were already yielded, we can't transparently retry
|
|
4640
|
-
if (chunksYielded) {
|
|
4641
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
4642
|
-
log$1.error("Stream failed after partial data was delivered");
|
|
4643
|
-
log$1.var({ error });
|
|
4644
|
-
yield {
|
|
4645
|
-
type: LlmStreamChunkType.Error,
|
|
4646
|
-
error: {
|
|
4647
|
-
detail: errorMessage,
|
|
4648
|
-
status: 502,
|
|
4649
|
-
title: "Stream Error",
|
|
4650
|
-
},
|
|
4651
|
-
};
|
|
4652
|
-
return { shouldContinue: false };
|
|
4653
|
-
}
|
|
4654
|
-
// Check if we've exhausted retries or error is not retryable
|
|
4655
|
-
if (!this.retryPolicy.shouldRetry(attempt) ||
|
|
4656
|
-
!this.adapter.isRetryableError(error)) {
|
|
4657
|
-
log$1.error(`Stream request failed after ${this.retryPolicy.maxRetries} retries`);
|
|
4702
|
+
const delay = this.retryPolicy.getDelayForAttempt(attempt);
|
|
4703
|
+
log$1.warn(`Stream request failed. Retrying in ${delay}ms...`);
|
|
4658
4704
|
log$1.var({ error });
|
|
4659
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
4660
|
-
throw new BadGatewayError(errorMessage);
|
|
4661
|
-
}
|
|
4662
|
-
const delay = this.retryPolicy.getDelayForAttempt(attempt);
|
|
4663
|
-
log$1.warn(`Stream request failed. Retrying in ${delay}ms...`);
|
|
4664
|
-
log$1.var({ error });
|
|
4665
|
-
// Guard against stale socket errors that fire during sleep
|
|
4666
|
-
const staleHandler = (reason) => {
|
|
4667
|
-
if (isTransientNetworkError(reason)) {
|
|
4668
|
-
log$1.trace("Suppressed stale socket error during retry sleep");
|
|
4669
|
-
}
|
|
4670
|
-
};
|
|
4671
|
-
process.on("unhandledRejection", staleHandler);
|
|
4672
|
-
try {
|
|
4673
4705
|
await sleep(delay);
|
|
4706
|
+
attempt++;
|
|
4674
4707
|
}
|
|
4675
|
-
finally {
|
|
4676
|
-
process.removeListener("unhandledRejection", staleHandler);
|
|
4677
|
-
}
|
|
4678
|
-
attempt++;
|
|
4679
4708
|
}
|
|
4680
4709
|
}
|
|
4710
|
+
finally {
|
|
4711
|
+
removeGuard();
|
|
4712
|
+
}
|
|
4681
4713
|
// Execute afterEachModelResponse hook
|
|
4682
4714
|
await this.hookRunnerInstance.runAfterModelResponse(context.hooks, {
|
|
4683
4715
|
content: "",
|