@jaypie/llm 1.2.10 → 1.2.12

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.
@@ -17,8 +17,8 @@ export declare class AnthropicAdapter extends BaseProviderAdapter {
17
17
  buildRequest(request: OperateRequest): Anthropic.MessageCreateParams;
18
18
  formatTools(toolkit: Toolkit, outputSchema?: JsonObject): ProviderToolDefinition[];
19
19
  formatOutputSchema(schema: JsonObject | NaturalSchema | z.ZodType): JsonObject;
20
- executeRequest(client: unknown, request: unknown): Promise<Anthropic.Message>;
21
- executeStreamRequest(client: unknown, request: unknown): AsyncIterable<LlmStreamChunk>;
20
+ executeRequest(client: unknown, request: unknown, signal?: AbortSignal): Promise<Anthropic.Message>;
21
+ executeStreamRequest(client: unknown, request: unknown, signal?: AbortSignal): AsyncIterable<LlmStreamChunk>;
22
22
  parseResponse(response: unknown, _options?: LlmOperateOptions): ParsedResponse;
23
23
  extractToolCalls(response: unknown): StandardToolCall[];
24
24
  extractUsage(response: unknown, model: string): LlmUsageItem;
@@ -17,8 +17,8 @@ export declare class GeminiAdapter extends BaseProviderAdapter {
17
17
  buildRequest(request: OperateRequest): GeminiRequest;
18
18
  formatTools(toolkit: Toolkit, outputSchema?: JsonObject): ProviderToolDefinition[];
19
19
  formatOutputSchema(schema: JsonObject | NaturalSchema | z.ZodType): JsonObject;
20
- executeRequest(client: unknown, request: unknown): Promise<GeminiRawResponse>;
21
- executeStreamRequest(client: unknown, request: unknown): AsyncIterable<LlmStreamChunk>;
20
+ executeRequest(client: unknown, request: unknown, signal?: AbortSignal): Promise<GeminiRawResponse>;
21
+ executeStreamRequest(client: unknown, request: unknown, signal?: AbortSignal): AsyncIterable<LlmStreamChunk>;
22
22
  parseResponse(response: unknown, options?: LlmOperateOptions): ParsedResponse;
23
23
  extractToolCalls(response: unknown): StandardToolCall[];
24
24
  extractUsage(response: unknown, model: string): LlmUsageItem;
@@ -16,8 +16,8 @@ export declare class OpenAiAdapter extends BaseProviderAdapter {
16
16
  buildRequest(request: OperateRequest): unknown;
17
17
  formatTools(toolkit: Toolkit, _outputSchema?: JsonObject): ProviderToolDefinition[];
18
18
  formatOutputSchema(schema: JsonObject | NaturalSchema | z.ZodType): JsonObject;
19
- executeRequest(client: unknown, request: unknown): Promise<unknown>;
20
- executeStreamRequest(client: unknown, request: unknown): AsyncIterable<LlmStreamChunk>;
19
+ executeRequest(client: unknown, request: unknown, signal?: AbortSignal): Promise<unknown>;
20
+ executeStreamRequest(client: unknown, request: unknown, signal?: AbortSignal): AsyncIterable<LlmStreamChunk>;
21
21
  parseResponse(response: unknown, options?: LlmOperateOptions): ParsedResponse;
22
22
  extractToolCalls(response: unknown): StandardToolCall[];
23
23
  extractUsage(response: unknown, model: string): LlmUsageItem;
@@ -88,8 +88,8 @@ export declare class OpenRouterAdapter extends BaseProviderAdapter {
88
88
  buildRequest(request: OperateRequest): OpenRouterRequest;
89
89
  formatTools(toolkit: Toolkit, outputSchema?: JsonObject): ProviderToolDefinition[];
90
90
  formatOutputSchema(schema: JsonObject | NaturalSchema | z.ZodType): JsonObject;
91
- executeRequest(client: unknown, request: unknown): Promise<OpenRouterResponse>;
92
- executeStreamRequest(client: unknown, request: unknown): AsyncIterable<LlmStreamChunk>;
91
+ executeRequest(client: unknown, request: unknown, signal?: AbortSignal): Promise<OpenRouterResponse>;
92
+ executeStreamRequest(client: unknown, request: unknown, signal?: AbortSignal): AsyncIterable<LlmStreamChunk>;
93
93
  parseResponse(response: unknown, _options?: LlmOperateOptions): ParsedResponse;
94
94
  extractToolCalls(response: unknown): StandardToolCall[];
95
95
  extractUsage(response: unknown, model: string): LlmUsageItem;
@@ -47,17 +47,19 @@ export interface ProviderAdapter {
47
47
  *
48
48
  * @param client - The provider's SDK client instance
49
49
  * @param request - Provider-specific request object (from buildRequest)
50
+ * @param signal - Optional AbortSignal to cancel the request on retry
50
51
  * @returns Raw provider response
51
52
  */
52
- executeRequest(client: unknown, request: unknown): Promise<unknown>;
53
+ executeRequest(client: unknown, request: unknown, signal?: AbortSignal): Promise<unknown>;
53
54
  /**
54
55
  * Execute a streaming API request to the provider
55
56
  *
56
57
  * @param client - The provider's SDK client instance
57
58
  * @param request - Provider-specific request object (from buildRequest)
59
+ * @param signal - Optional AbortSignal to cancel the request on retry
58
60
  * @returns AsyncIterable of stream chunks
59
61
  */
60
- executeStreamRequest?(client: unknown, request: unknown): AsyncIterable<LlmStreamChunk>;
62
+ executeStreamRequest?(client: unknown, request: unknown, signal?: AbortSignal): AsyncIterable<LlmStreamChunk>;
61
63
  /**
62
64
  * Parse a provider response into standardized format
63
65
  *
@@ -158,7 +160,7 @@ export declare abstract class BaseProviderAdapter implements ProviderAdapter {
158
160
  abstract buildRequest(request: OperateRequest): unknown;
159
161
  abstract formatTools(toolkit: Toolkit, outputSchema?: JsonObject): ProviderToolDefinition[];
160
162
  abstract formatOutputSchema(schema: JsonObject | NaturalSchema | z.ZodType): JsonObject;
161
- abstract executeRequest(client: unknown, request: unknown): Promise<unknown>;
163
+ abstract executeRequest(client: unknown, request: unknown, signal?: AbortSignal): Promise<unknown>;
162
164
  abstract parseResponse(response: unknown, options?: LlmOperateOptions): ParsedResponse;
163
165
  abstract extractToolCalls(response: unknown): StandardToolCall[];
164
166
  abstract extractUsage(response: unknown, model: string): LlmUsageItem;
@@ -28,12 +28,15 @@ export declare class RetryExecutor {
28
28
  private readonly errorClassifier;
29
29
  constructor(config: RetryExecutorConfig);
30
30
  /**
31
- * Execute an operation with retry logic
31
+ * Execute an operation with retry logic.
32
+ * Each attempt receives an AbortSignal. On failure, the signal is aborted
33
+ * before sleeping — this kills lingering socket callbacks from the previous
34
+ * request and prevents stale async errors from escaping the retry loop.
32
35
  *
33
- * @param operation - The async operation to execute
36
+ * @param operation - The async operation to execute (receives AbortSignal)
34
37
  * @param options - Execution options including context and hooks
35
38
  * @returns The result of the operation
36
39
  * @throws BadGatewayError if all retries are exhausted or error is not retryable
37
40
  */
38
- execute<T>(operation: () => Promise<T>, options: ExecuteOptions): Promise<T>;
41
+ execute<T>(operation: ((signal: AbortSignal) => Promise<T>) | (() => Promise<T>), options: ExecuteOptions): Promise<T>;
39
42
  }
package/dist/esm/index.js CHANGED
@@ -1126,17 +1126,24 @@ class AnthropicAdapter extends BaseProviderAdapter {
1126
1126
  //
1127
1127
  // API Execution
1128
1128
  //
1129
- async executeRequest(client, request) {
1129
+ async executeRequest(client, request, signal) {
1130
1130
  const anthropic = client;
1131
- return (await anthropic.messages.create(request));
1131
+ try {
1132
+ return (await anthropic.messages.create(request, signal ? { signal } : undefined));
1133
+ }
1134
+ catch (error) {
1135
+ if (signal?.aborted)
1136
+ return undefined;
1137
+ throw error;
1138
+ }
1132
1139
  }
1133
- async *executeStreamRequest(client, request) {
1140
+ async *executeStreamRequest(client, request, signal) {
1134
1141
  const anthropic = client;
1135
1142
  const streamRequest = {
1136
1143
  ...request,
1137
1144
  stream: true,
1138
1145
  };
1139
- const stream = await anthropic.messages.create(streamRequest);
1146
+ const stream = await anthropic.messages.create(streamRequest, signal ? { signal } : undefined);
1140
1147
  // Track current tool call being built
1141
1148
  let currentToolCall = null;
1142
1149
  // Track usage for final chunk
@@ -1566,19 +1573,26 @@ class GeminiAdapter extends BaseProviderAdapter {
1566
1573
  //
1567
1574
  // API Execution
1568
1575
  //
1569
- async executeRequest(client, request) {
1576
+ async executeRequest(client, request, signal) {
1570
1577
  const genAI = client;
1571
1578
  const geminiRequest = request;
1572
- // Cast config to any to bypass strict type checking between our internal types
1573
- // and the SDK's types. The SDK will validate at runtime.
1574
- const response = await genAI.models.generateContent({
1575
- model: geminiRequest.model,
1576
- contents: geminiRequest.contents,
1577
- config: geminiRequest.config,
1578
- });
1579
- return response;
1579
+ try {
1580
+ // Cast config to any to bypass strict type checking between our internal types
1581
+ // and the SDK's types. The SDK will validate at runtime.
1582
+ const response = await genAI.models.generateContent({
1583
+ model: geminiRequest.model,
1584
+ contents: geminiRequest.contents,
1585
+ config: geminiRequest.config,
1586
+ });
1587
+ return response;
1588
+ }
1589
+ catch (error) {
1590
+ if (signal?.aborted)
1591
+ return undefined;
1592
+ throw error;
1593
+ }
1580
1594
  }
1581
- async *executeStreamRequest(client, request) {
1595
+ async *executeStreamRequest(client, request, signal) {
1582
1596
  const genAI = client;
1583
1597
  const geminiRequest = request;
1584
1598
  // Use generateContentStream for streaming
@@ -2228,19 +2242,26 @@ class OpenAiAdapter extends BaseProviderAdapter {
2228
2242
  //
2229
2243
  // API Execution
2230
2244
  //
2231
- async executeRequest(client, request) {
2245
+ async executeRequest(client, request, signal) {
2232
2246
  const openai = client;
2233
- // @ts-expect-error OpenAI SDK types don't match our request format exactly
2234
- return await openai.responses.create(request);
2247
+ try {
2248
+ // @ts-expect-error OpenAI SDK types don't match our request format exactly
2249
+ return await openai.responses.create(request, signal ? { signal } : undefined);
2250
+ }
2251
+ catch (error) {
2252
+ if (signal?.aborted)
2253
+ return undefined;
2254
+ throw error;
2255
+ }
2235
2256
  }
2236
- async *executeStreamRequest(client, request) {
2257
+ async *executeStreamRequest(client, request, signal) {
2237
2258
  const openai = client;
2238
2259
  const baseRequest = request;
2239
2260
  const streamRequest = {
2240
2261
  ...baseRequest,
2241
2262
  stream: true,
2242
2263
  };
2243
- const stream = await openai.responses.create(streamRequest);
2264
+ const stream = await openai.responses.create(streamRequest, signal ? { signal } : undefined);
2244
2265
  // Track current function call being built
2245
2266
  let currentFunctionCall = null;
2246
2267
  // Track usage for final chunk
@@ -2671,19 +2692,26 @@ class OpenRouterAdapter extends BaseProviderAdapter {
2671
2692
  //
2672
2693
  // API Execution
2673
2694
  //
2674
- async executeRequest(client, request) {
2695
+ async executeRequest(client, request, signal) {
2675
2696
  const openRouter = client;
2676
2697
  const openRouterRequest = request;
2677
- const response = await openRouter.chat.send({
2678
- model: openRouterRequest.model,
2679
- messages: openRouterRequest.messages,
2680
- tools: openRouterRequest.tools,
2681
- toolChoice: openRouterRequest.tool_choice,
2682
- user: openRouterRequest.user,
2683
- });
2684
- return response;
2698
+ try {
2699
+ const response = await openRouter.chat.send({
2700
+ model: openRouterRequest.model,
2701
+ messages: openRouterRequest.messages,
2702
+ tools: openRouterRequest.tools,
2703
+ toolChoice: openRouterRequest.tool_choice,
2704
+ user: openRouterRequest.user,
2705
+ }, signal ? { signal } : undefined);
2706
+ return response;
2707
+ }
2708
+ catch (error) {
2709
+ if (signal?.aborted)
2710
+ return undefined;
2711
+ throw error;
2712
+ }
2685
2713
  }
2686
- async *executeStreamRequest(client, request) {
2714
+ async *executeStreamRequest(client, request, signal) {
2687
2715
  const openRouter = client;
2688
2716
  const openRouterRequest = request;
2689
2717
  // Use chat.send with stream: true for streaming responses
@@ -2694,7 +2722,7 @@ class OpenRouterAdapter extends BaseProviderAdapter {
2694
2722
  toolChoice: openRouterRequest.tool_choice,
2695
2723
  user: openRouterRequest.user,
2696
2724
  stream: true,
2697
- });
2725
+ }, signal ? { signal } : undefined);
2698
2726
  // Track current tool call being built
2699
2727
  let currentToolCall = null;
2700
2728
  // Track usage for final chunk
@@ -3905,9 +3933,12 @@ class RetryExecutor {
3905
3933
  this.errorClassifier = config.errorClassifier;
3906
3934
  }
3907
3935
  /**
3908
- * Execute an operation with retry logic
3936
+ * Execute an operation with retry logic.
3937
+ * Each attempt receives an AbortSignal. On failure, the signal is aborted
3938
+ * before sleeping — this kills lingering socket callbacks from the previous
3939
+ * request and prevents stale async errors from escaping the retry loop.
3909
3940
  *
3910
- * @param operation - The async operation to execute
3941
+ * @param operation - The async operation to execute (receives AbortSignal)
3911
3942
  * @param options - Execution options including context and hooks
3912
3943
  * @returns The result of the operation
3913
3944
  * @throws BadGatewayError if all retries are exhausted or error is not retryable
@@ -3915,14 +3946,17 @@ class RetryExecutor {
3915
3946
  async execute(operation, options) {
3916
3947
  let attempt = 0;
3917
3948
  while (true) {
3949
+ const controller = new AbortController();
3918
3950
  try {
3919
- const result = await operation();
3951
+ const result = await operation(controller.signal);
3920
3952
  if (attempt > 0) {
3921
3953
  log$1.debug(`API call succeeded after ${attempt} retries`);
3922
3954
  }
3923
3955
  return result;
3924
3956
  }
3925
3957
  catch (error) {
3958
+ // Abort the previous request to kill lingering socket callbacks
3959
+ controller.abort("retry");
3926
3960
  // Check if we've exhausted retries
3927
3961
  if (!this.policy.shouldRetry(attempt)) {
3928
3962
  log$1.error(`API call failed after ${this.policy.maxRetries} retries`);
@@ -3962,7 +3996,19 @@ class RetryExecutor {
3962
3996
  providerRequest: options.context.providerRequest,
3963
3997
  error,
3964
3998
  });
3965
- await sleep(delay);
3999
+ // Guard against stale socket errors that fire during sleep
4000
+ const staleHandler = (reason) => {
4001
+ if (isTransientNetworkError(reason)) {
4002
+ log$1.trace("Suppressed stale socket error during retry sleep");
4003
+ }
4004
+ };
4005
+ process.on("unhandledRejection", staleHandler);
4006
+ try {
4007
+ await sleep(delay);
4008
+ }
4009
+ finally {
4010
+ process.removeListener("unhandledRejection", staleHandler);
4011
+ }
3966
4012
  attempt++;
3967
4013
  }
3968
4014
  }
@@ -4147,7 +4193,7 @@ class OperateLoop {
4147
4193
  providerRequest,
4148
4194
  });
4149
4195
  // Execute with retry (RetryExecutor handles error hooks and throws appropriate errors)
4150
- const response = await retryExecutor.execute(() => this.adapter.executeRequest(this.client, providerRequest), {
4196
+ const response = await retryExecutor.execute((signal) => this.adapter.executeRequest(this.client, providerRequest, signal), {
4151
4197
  context: {
4152
4198
  input: state.currentInput,
4153
4199
  options,
@@ -4540,9 +4586,10 @@ class StreamLoop {
4540
4586
  let attempt = 0;
4541
4587
  let chunksYielded = false;
4542
4588
  while (true) {
4589
+ const controller = new AbortController();
4543
4590
  try {
4544
4591
  // Execute streaming request
4545
- const streamGenerator = this.adapter.executeStreamRequest(this.client, providerRequest);
4592
+ const streamGenerator = this.adapter.executeStreamRequest(this.client, providerRequest, controller.signal);
4546
4593
  for await (const chunk of streamGenerator) {
4547
4594
  // Pass through text chunks
4548
4595
  if (chunk.type === LlmStreamChunkType.Text) {
@@ -4577,6 +4624,8 @@ class StreamLoop {
4577
4624
  break;
4578
4625
  }
4579
4626
  catch (error) {
4627
+ // Abort the previous request to kill lingering socket callbacks
4628
+ controller.abort("retry");
4580
4629
  // If chunks were already yielded, we can't transparently retry
4581
4630
  if (chunksYielded) {
4582
4631
  const errorMessage = error instanceof Error ? error.message : String(error);
@@ -4603,7 +4652,19 @@ class StreamLoop {
4603
4652
  const delay = this.retryPolicy.getDelayForAttempt(attempt);
4604
4653
  log$1.warn(`Stream request failed. Retrying in ${delay}ms...`);
4605
4654
  log$1.var({ error });
4606
- await sleep(delay);
4655
+ // Guard against stale socket errors that fire during sleep
4656
+ const staleHandler = (reason) => {
4657
+ if (isTransientNetworkError(reason)) {
4658
+ log$1.trace("Suppressed stale socket error during retry sleep");
4659
+ }
4660
+ };
4661
+ process.on("unhandledRejection", staleHandler);
4662
+ try {
4663
+ await sleep(delay);
4664
+ }
4665
+ finally {
4666
+ process.removeListener("unhandledRejection", staleHandler);
4667
+ }
4607
4668
  attempt++;
4608
4669
  }
4609
4670
  }