@jaypie/llm 1.2.8 → 1.2.10

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.
@@ -807,6 +807,90 @@ var ErrorCategory;
807
807
  ErrorCategory["Unknown"] = "unknown";
808
808
  })(ErrorCategory || (ErrorCategory = {}));
809
809
 
810
+ /**
811
+ * Transient network error detection utility.
812
+ *
813
+ * Detects low-level Node.js/undici network errors that indicate
814
+ * a temporary network issue (not a provider API error).
815
+ * These errors should always be retried.
816
+ */
817
+ //
818
+ //
819
+ // Constants
820
+ //
821
+ /** Error codes from Node.js net/dns subsystems that indicate transient failures */
822
+ const TRANSIENT_ERROR_CODES = new Set([
823
+ "ECONNREFUSED",
824
+ "ECONNRESET",
825
+ "EAI_AGAIN",
826
+ "ENETRESET",
827
+ "ENETUNREACH",
828
+ "ENOTFOUND",
829
+ "EPIPE",
830
+ "ETIMEDOUT",
831
+ ]);
832
+ /** Substrings in error messages that indicate transient network issues */
833
+ const TRANSIENT_MESSAGE_PATTERNS = [
834
+ "network",
835
+ "socket hang up",
836
+ "terminated",
837
+ ];
838
+ //
839
+ //
840
+ // Helpers
841
+ //
842
+ /**
843
+ * Check a single error (without walking the cause chain)
844
+ */
845
+ function matchesSingleError(error) {
846
+ if (!(error instanceof Error))
847
+ return false;
848
+ // Check error code (e.g., ECONNRESET)
849
+ const code = error.code;
850
+ if (code && TRANSIENT_ERROR_CODES.has(code)) {
851
+ return true;
852
+ }
853
+ // Check error message for transient patterns
854
+ const message = error.message.toLowerCase();
855
+ for (const pattern of TRANSIENT_MESSAGE_PATTERNS) {
856
+ if (message.includes(pattern)) {
857
+ return true;
858
+ }
859
+ }
860
+ return false;
861
+ }
862
+ //
863
+ //
864
+ // Main
865
+ //
866
+ /**
867
+ * Detect transient network errors by inspecting the error and its cause chain.
868
+ *
869
+ * Undici (Node.js fetch) wraps low-level errors like ECONNRESET inside
870
+ * `TypeError: terminated`. This function recursively walks `error.cause`
871
+ * to detect these wrapped errors.
872
+ *
873
+ * @param error - The error to inspect
874
+ * @returns true if the error (or any cause in its chain) is a transient network error
875
+ */
876
+ function isTransientNetworkError(error) {
877
+ let current = error;
878
+ while (current) {
879
+ if (matchesSingleError(current)) {
880
+ return true;
881
+ }
882
+ // Walk the cause chain (cause is ES2022, cast for compatibility)
883
+ const cause = current.cause;
884
+ if (current instanceof Error && cause) {
885
+ current = cause;
886
+ }
887
+ else {
888
+ break;
889
+ }
890
+ }
891
+ return false;
892
+ }
893
+
810
894
  //
811
895
  //
812
896
  // Constants
@@ -992,6 +1076,10 @@ class AnthropicAdapter extends BaseProviderAdapter {
992
1076
  if (request.providerOptions) {
993
1077
  Object.assign(anthropicRequest, request.providerOptions);
994
1078
  }
1079
+ // First-class temperature takes precedence over providerOptions
1080
+ if (request.temperature !== undefined) {
1081
+ anthropicRequest.temperature = request.temperature;
1082
+ }
995
1083
  return anthropicRequest;
996
1084
  }
997
1085
  formatTools(toolkit, outputSchema) {
@@ -1264,6 +1352,14 @@ class AnthropicAdapter extends BaseProviderAdapter {
1264
1352
  shouldRetry: false,
1265
1353
  };
1266
1354
  }
1355
+ // Check for transient network errors (ECONNRESET, etc.)
1356
+ if (isTransientNetworkError(error)) {
1357
+ return {
1358
+ error,
1359
+ category: ErrorCategory.Retryable,
1360
+ shouldRetry: true,
1361
+ };
1362
+ }
1267
1363
  // Unknown error - treat as potentially retryable
1268
1364
  return {
1269
1365
  error,
@@ -1417,6 +1513,13 @@ class GeminiAdapter extends BaseProviderAdapter {
1417
1513
  ...request.providerOptions,
1418
1514
  };
1419
1515
  }
1516
+ // First-class temperature takes precedence over providerOptions
1517
+ if (request.temperature !== undefined) {
1518
+ geminiRequest.config = {
1519
+ ...geminiRequest.config,
1520
+ temperature: request.temperature,
1521
+ };
1522
+ }
1420
1523
  return geminiRequest;
1421
1524
  }
1422
1525
  formatTools(toolkit, outputSchema) {
@@ -1739,6 +1842,14 @@ class GeminiAdapter extends BaseProviderAdapter {
1739
1842
  shouldRetry: true,
1740
1843
  };
1741
1844
  }
1845
+ // Check for transient network errors (ECONNRESET, etc.)
1846
+ if (isTransientNetworkError(error)) {
1847
+ return {
1848
+ error,
1849
+ category: ErrorCategory.Retryable,
1850
+ shouldRetry: true,
1851
+ };
1852
+ }
1742
1853
  // Unknown error - treat as potentially retryable
1743
1854
  return {
1744
1855
  error,
@@ -2068,6 +2179,10 @@ class OpenAiAdapter extends BaseProviderAdapter {
2068
2179
  if (request.providerOptions) {
2069
2180
  Object.assign(openaiRequest, request.providerOptions);
2070
2181
  }
2182
+ // First-class temperature takes precedence over providerOptions
2183
+ if (request.temperature !== undefined) {
2184
+ openaiRequest.temperature = request.temperature;
2185
+ }
2071
2186
  return openaiRequest;
2072
2187
  }
2073
2188
  formatTools(toolkit, _outputSchema) {
@@ -2337,6 +2452,14 @@ class OpenAiAdapter extends BaseProviderAdapter {
2337
2452
  };
2338
2453
  }
2339
2454
  }
2455
+ // Check for transient network errors (ECONNRESET, etc.)
2456
+ if (isTransientNetworkError(error)) {
2457
+ return {
2458
+ error,
2459
+ category: ErrorCategory.Retryable,
2460
+ shouldRetry: true,
2461
+ };
2462
+ }
2340
2463
  // Unknown error - treat as potentially retryable
2341
2464
  return {
2342
2465
  error,
@@ -2498,6 +2621,11 @@ class OpenRouterAdapter extends BaseProviderAdapter {
2498
2621
  if (request.providerOptions) {
2499
2622
  Object.assign(openRouterRequest, request.providerOptions);
2500
2623
  }
2624
+ // First-class temperature takes precedence over providerOptions
2625
+ if (request.temperature !== undefined) {
2626
+ openRouterRequest.temperature =
2627
+ request.temperature;
2628
+ }
2501
2629
  return openRouterRequest;
2502
2630
  }
2503
2631
  formatTools(toolkit, outputSchema) {
@@ -2818,6 +2946,14 @@ class OpenRouterAdapter extends BaseProviderAdapter {
2818
2946
  suggestedDelayMs: 60000,
2819
2947
  };
2820
2948
  }
2949
+ // Check for transient network errors (ECONNRESET, etc.)
2950
+ if (isTransientNetworkError(error)) {
2951
+ return {
2952
+ error,
2953
+ category: ErrorCategory.Retryable,
2954
+ shouldRetry: true,
2955
+ };
2956
+ }
2821
2957
  // Unknown error - treat as potentially retryable
2822
2958
  return {
2823
2959
  error,
@@ -3907,6 +4043,7 @@ class OperateLoop {
3907
4043
  model: options.model ?? this.adapter.defaultModel,
3908
4044
  providerOptions: options.providerOptions,
3909
4045
  system: options.system,
4046
+ temperature: options.temperature,
3910
4047
  tools: state.formattedTools,
3911
4048
  user: options.user,
3912
4049
  };
@@ -3986,6 +4123,7 @@ class OperateLoop {
3986
4123
  model: options.model ?? this.adapter.defaultModel,
3987
4124
  providerOptions: options.providerOptions,
3988
4125
  system: options.system,
4126
+ temperature: options.temperature,
3989
4127
  tools: state.formattedTools,
3990
4128
  user: options.user,
3991
4129
  };
@@ -4267,6 +4405,7 @@ class StreamLoop {
4267
4405
  this.client = config.client;
4268
4406
  this.hookRunnerInstance = config.hookRunner ?? hookRunner;
4269
4407
  this.inputProcessorInstance = config.inputProcessor ?? inputProcessor;
4408
+ this.retryPolicy = config.retryPolicy ?? defaultRetryPolicy;
4270
4409
  }
4271
4410
  /**
4272
4411
  * Execute the streaming loop for multi-turn conversations with tool calling.
@@ -4316,6 +4455,7 @@ class StreamLoop {
4316
4455
  model: options.model ?? this.adapter.defaultModel,
4317
4456
  providerOptions: options.providerOptions,
4318
4457
  system: options.system,
4458
+ temperature: options.temperature,
4319
4459
  tools: state.formattedTools,
4320
4460
  user: options.user,
4321
4461
  };
@@ -4382,6 +4522,7 @@ class StreamLoop {
4382
4522
  model: options.model ?? this.adapter.defaultModel,
4383
4523
  providerOptions: options.providerOptions,
4384
4524
  system: options.system,
4525
+ temperature: options.temperature,
4385
4526
  tools: state.formattedTools,
4386
4527
  user: options.user,
4387
4528
  };
@@ -4397,30 +4538,75 @@ class StreamLoop {
4397
4538
  });
4398
4539
  // Collect tool calls from the stream
4399
4540
  const collectedToolCalls = [];
4400
- // Execute streaming request
4401
- const streamGenerator = this.adapter.executeStreamRequest(this.client, providerRequest);
4402
- for await (const chunk of streamGenerator) {
4403
- // Pass through text chunks
4404
- if (chunk.type === exports.LlmStreamChunkType.Text) {
4405
- yield chunk;
4406
- }
4407
- // Collect tool calls
4408
- if (chunk.type === exports.LlmStreamChunkType.ToolCall) {
4409
- collectedToolCalls.push({
4410
- callId: chunk.toolCall.id,
4411
- name: chunk.toolCall.name,
4412
- arguments: chunk.toolCall.arguments,
4413
- raw: chunk.toolCall,
4414
- });
4415
- yield chunk;
4416
- }
4417
- // Track usage from done chunk (but don't yield it yet - we'll emit our own)
4418
- if (chunk.type === exports.LlmStreamChunkType.Done && chunk.usage) {
4419
- state.usageItems.push(...chunk.usage);
4541
+ // Retry loop for connection-level failures
4542
+ let attempt = 0;
4543
+ let chunksYielded = false;
4544
+ while (true) {
4545
+ try {
4546
+ // Execute streaming request
4547
+ const streamGenerator = this.adapter.executeStreamRequest(this.client, providerRequest);
4548
+ for await (const chunk of streamGenerator) {
4549
+ // Pass through text chunks
4550
+ if (chunk.type === exports.LlmStreamChunkType.Text) {
4551
+ chunksYielded = true;
4552
+ yield chunk;
4553
+ }
4554
+ // Collect tool calls
4555
+ if (chunk.type === exports.LlmStreamChunkType.ToolCall) {
4556
+ chunksYielded = true;
4557
+ collectedToolCalls.push({
4558
+ callId: chunk.toolCall.id,
4559
+ name: chunk.toolCall.name,
4560
+ arguments: chunk.toolCall.arguments,
4561
+ raw: chunk.toolCall,
4562
+ });
4563
+ yield chunk;
4564
+ }
4565
+ // Track usage from done chunk (but don't yield it yet - we'll emit our own)
4566
+ if (chunk.type === exports.LlmStreamChunkType.Done && chunk.usage) {
4567
+ state.usageItems.push(...chunk.usage);
4568
+ }
4569
+ // Pass through error chunks
4570
+ if (chunk.type === exports.LlmStreamChunkType.Error) {
4571
+ chunksYielded = true;
4572
+ yield chunk;
4573
+ }
4574
+ }
4575
+ // Stream completed successfully
4576
+ if (attempt > 0) {
4577
+ log$1.debug(`Stream request succeeded after ${attempt} retries`);
4578
+ }
4579
+ break;
4420
4580
  }
4421
- // Pass through error chunks
4422
- if (chunk.type === exports.LlmStreamChunkType.Error) {
4423
- yield chunk;
4581
+ catch (error) {
4582
+ // If chunks were already yielded, we can't transparently retry
4583
+ if (chunksYielded) {
4584
+ const errorMessage = error instanceof Error ? error.message : String(error);
4585
+ log$1.error("Stream failed after partial data was delivered");
4586
+ log$1.var({ error });
4587
+ yield {
4588
+ type: exports.LlmStreamChunkType.Error,
4589
+ error: {
4590
+ detail: errorMessage,
4591
+ status: 502,
4592
+ title: "Stream Error",
4593
+ },
4594
+ };
4595
+ return { shouldContinue: false };
4596
+ }
4597
+ // Check if we've exhausted retries or error is not retryable
4598
+ if (!this.retryPolicy.shouldRetry(attempt) ||
4599
+ !this.adapter.isRetryableError(error)) {
4600
+ log$1.error(`Stream request failed after ${this.retryPolicy.maxRetries} retries`);
4601
+ log$1.var({ error });
4602
+ const errorMessage = error instanceof Error ? error.message : String(error);
4603
+ throw new errors.BadGatewayError(errorMessage);
4604
+ }
4605
+ const delay = this.retryPolicy.getDelayForAttempt(attempt);
4606
+ log$1.warn(`Stream request failed. Retrying in ${delay}ms...`);
4607
+ log$1.var({ error });
4608
+ await kit.sleep(delay);
4609
+ attempt++;
4424
4610
  }
4425
4611
  }
4426
4612
  // Execute afterEachModelResponse hook