@jaypie/llm 1.2.20 → 1.2.22

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.
Files changed (35) hide show
  1. package/dist/cjs/constants.d.ts +31 -31
  2. package/dist/cjs/index.cjs +58 -29
  3. package/dist/cjs/index.cjs.map +1 -1
  4. package/dist/cjs/operate/OperateLoop.d.ts +1 -0
  5. package/dist/cjs/operate/adapters/AnthropicAdapter.d.ts +1 -1
  6. package/dist/cjs/operate/adapters/GeminiAdapter.d.ts +1 -1
  7. package/dist/cjs/operate/adapters/OpenAiAdapter.d.ts +1 -1
  8. package/dist/cjs/operate/adapters/OpenRouterAdapter.d.ts +1 -1
  9. package/dist/cjs/operate/adapters/XaiAdapter.d.ts +1 -1
  10. package/dist/cjs/operate/types.d.ts +2 -0
  11. package/dist/cjs/providers/anthropic/utils.d.ts +1 -1
  12. package/dist/cjs/providers/gemini/utils.d.ts +1 -1
  13. package/dist/cjs/providers/openai/utils.d.ts +1 -1
  14. package/dist/cjs/providers/openrouter/utils.d.ts +1 -1
  15. package/dist/cjs/providers/xai/utils.d.ts +1 -1
  16. package/dist/cjs/util/logger.d.ts +2 -2
  17. package/dist/cjs/util/maxTurnsFromOptions.d.ts +1 -1
  18. package/dist/esm/constants.d.ts +31 -31
  19. package/dist/esm/index.js +58 -29
  20. package/dist/esm/index.js.map +1 -1
  21. package/dist/esm/operate/OperateLoop.d.ts +1 -0
  22. package/dist/esm/operate/adapters/AnthropicAdapter.d.ts +1 -1
  23. package/dist/esm/operate/adapters/GeminiAdapter.d.ts +1 -1
  24. package/dist/esm/operate/adapters/OpenAiAdapter.d.ts +1 -1
  25. package/dist/esm/operate/adapters/OpenRouterAdapter.d.ts +1 -1
  26. package/dist/esm/operate/adapters/XaiAdapter.d.ts +1 -1
  27. package/dist/esm/operate/types.d.ts +2 -0
  28. package/dist/esm/providers/anthropic/utils.d.ts +1 -1
  29. package/dist/esm/providers/gemini/utils.d.ts +1 -1
  30. package/dist/esm/providers/openai/utils.d.ts +1 -1
  31. package/dist/esm/providers/openrouter/utils.d.ts +1 -1
  32. package/dist/esm/providers/xai/utils.d.ts +1 -1
  33. package/dist/esm/util/logger.d.ts +2 -2
  34. package/dist/esm/util/maxTurnsFromOptions.d.ts +1 -1
  35. package/package.json +1 -1
@@ -10,6 +10,7 @@ export interface OperateLoopConfig {
10
10
  inputProcessor?: InputProcessor;
11
11
  retryPolicy?: RetryPolicy;
12
12
  }
13
+ export declare const MAX_CONSECUTIVE_TOOL_ERRORS = 6;
13
14
  /**
14
15
  * OperateLoop implements the core multi-turn conversation loop.
15
16
  * It orchestrates provider adapters, retry logic, hook execution, and tool calling.
@@ -13,7 +13,7 @@ import { BaseProviderAdapter } from "./ProviderAdapter.interface.js";
13
13
  */
14
14
  export declare class AnthropicAdapter extends BaseProviderAdapter {
15
15
  readonly name: "anthropic";
16
- readonly defaultModel: "claude-sonnet-4-5";
16
+ readonly defaultModel: "claude-sonnet-4-6";
17
17
  buildRequest(request: OperateRequest): Anthropic.MessageCreateParams;
18
18
  formatTools(toolkit: Toolkit, outputSchema?: JsonObject): ProviderToolDefinition[];
19
19
  formatOutputSchema(schema: JsonObject | NaturalSchema | z.ZodType): JsonObject;
@@ -13,7 +13,7 @@ import { GeminiPart, GeminiRawResponse, GeminiRequest } from "../../providers/ge
13
13
  */
14
14
  export declare class GeminiAdapter extends BaseProviderAdapter {
15
15
  readonly name: "google";
16
- readonly defaultModel: "gemini-3-pro-preview";
16
+ readonly defaultModel: "gemini-3.1-pro-preview";
17
17
  buildRequest(request: OperateRequest): GeminiRequest;
18
18
  formatTools(toolkit: Toolkit, outputSchema?: JsonObject): ProviderToolDefinition[];
19
19
  formatOutputSchema(schema: JsonObject | NaturalSchema | z.ZodType): JsonObject;
@@ -12,7 +12,7 @@ import { BaseProviderAdapter } from "./ProviderAdapter.interface.js";
12
12
  */
13
13
  export declare class OpenAiAdapter extends BaseProviderAdapter {
14
14
  readonly name: "openai";
15
- readonly defaultModel: "gpt-5.2";
15
+ readonly defaultModel: "gpt-5.4";
16
16
  buildRequest(request: OperateRequest): unknown;
17
17
  formatTools(toolkit: Toolkit, _outputSchema?: JsonObject): ProviderToolDefinition[];
18
18
  formatOutputSchema(schema: JsonObject | NaturalSchema | z.ZodType): JsonObject;
@@ -84,7 +84,7 @@ type OpenRouterContentPart = {
84
84
  */
85
85
  export declare class OpenRouterAdapter extends BaseProviderAdapter {
86
86
  readonly name: "openrouter";
87
- readonly defaultModel: "z-ai/glm-4.7";
87
+ readonly defaultModel: "anthropic/claude-sonnet-4-6";
88
88
  buildRequest(request: OperateRequest): OpenRouterRequest;
89
89
  formatTools(toolkit: Toolkit, outputSchema?: JsonObject): ProviderToolDefinition[];
90
90
  formatOutputSchema(schema: JsonObject | NaturalSchema | z.ZodType): JsonObject;
@@ -6,6 +6,6 @@ import { OpenAiAdapter } from "./OpenAiAdapter.js";
6
6
  */
7
7
  export declare class XaiAdapter extends OpenAiAdapter {
8
8
  readonly name: "xai";
9
- readonly defaultModel: "grok-4-1-fast-reasoning";
9
+ readonly defaultModel: "grok-4.20-0309-reasoning";
10
10
  }
11
11
  export declare const xaiAdapter: XaiAdapter;
@@ -117,6 +117,8 @@ import type { ResponseBuilder } from "./response/ResponseBuilder.js";
117
117
  * Internal state of the operate loop
118
118
  */
119
119
  export interface OperateLoopState {
120
+ /** Count of consecutive tool errors (resets on success) */
121
+ consecutiveToolErrors: number;
120
122
  /** Current conversation input/messages */
121
123
  currentInput: LlmHistory;
122
124
  /** Current turn number (0-indexed, incremented at start of each turn) */
@@ -3,7 +3,7 @@ import { LlmMessageOptions } from "../../types/LlmProvider.interface.js";
3
3
  import { z } from "zod/v4";
4
4
  import { JsonObject, NaturalSchema } from "@jaypie/types";
5
5
  export declare function loadSdk(): Promise<typeof import("@anthropic-ai/sdk")>;
6
- export declare const getLogger: () => any;
6
+ export declare const getLogger: () => import("@jaypie/logger/dist/esm/JaypieLogger.js").default;
7
7
  export declare function initializeClient({ apiKey, }?: {
8
8
  apiKey?: string;
9
9
  }): Promise<Anthropic>;
@@ -1,7 +1,7 @@
1
1
  import type { GoogleGenAI } from "@google/genai";
2
2
  import { LlmMessageOptions } from "../../types/LlmProvider.interface.js";
3
3
  export declare function loadSdk(): Promise<typeof import("@google/genai")>;
4
- export declare const getLogger: () => any;
4
+ export declare const getLogger: () => import("@jaypie/logger/dist/esm/JaypieLogger.js").default;
5
5
  export declare function initializeClient({ apiKey, }?: {
6
6
  apiKey?: string;
7
7
  }): Promise<GoogleGenAI>;
@@ -2,7 +2,7 @@ import { JsonObject, NaturalSchema } from "@jaypie/types";
2
2
  import { OpenAI } from "openai";
3
3
  import { z } from "zod/v4";
4
4
  import { LlmMessageOptions } from "../../types/LlmProvider.interface.js";
5
- export declare const getLogger: () => any;
5
+ export declare const getLogger: () => import("@jaypie/logger/dist/esm/JaypieLogger.js").default;
6
6
  export declare function initializeClient({ apiKey, }?: {
7
7
  apiKey?: string;
8
8
  }): Promise<OpenAI>;
@@ -1,7 +1,7 @@
1
1
  import type { OpenRouter } from "@openrouter/sdk";
2
2
  import { LlmMessageOptions } from "../../types/LlmProvider.interface.js";
3
3
  export declare function loadSdk(): Promise<typeof import("@openrouter/sdk")>;
4
- export declare const getLogger: () => any;
4
+ export declare const getLogger: () => import("@jaypie/logger/dist/esm/JaypieLogger.js").default;
5
5
  export declare function initializeClient({ apiKey, }?: {
6
6
  apiKey?: string;
7
7
  }): Promise<OpenRouter>;
@@ -1,5 +1,5 @@
1
1
  import { OpenAI } from "openai";
2
- export declare const getLogger: () => any;
2
+ export declare const getLogger: () => import("@jaypie/logger/dist/esm/JaypieLogger.js").default;
3
3
  export declare function initializeClient({ apiKey, }?: {
4
4
  apiKey?: string;
5
5
  }): Promise<OpenAI>;
@@ -1,2 +1,2 @@
1
- export declare const getLogger: () => any;
2
- export declare const log: any;
1
+ export declare const getLogger: () => import("@jaypie/logger/dist/esm/JaypieLogger").default;
2
+ export declare const log: import("@jaypie/logger/dist/esm/JaypieLogger").default;
@@ -1,6 +1,6 @@
1
1
  import { LlmOperateOptions } from "../types/LlmProvider.interface.js";
2
2
  export declare const MAX_TURNS_ABSOLUTE_LIMIT = 72;
3
- export declare const MAX_TURNS_DEFAULT_LIMIT = 12;
3
+ export declare const MAX_TURNS_DEFAULT_LIMIT = 24;
4
4
  /**
5
5
  * Determines the maximum number of turns based on the provided options
6
6
  *
@@ -4,9 +4,9 @@ export declare const PROVIDER: {
4
4
  readonly DEFAULT: 4096;
5
5
  };
6
6
  readonly MODEL: {
7
- readonly DEFAULT: "claude-sonnet-4-5";
8
- readonly LARGE: "claude-opus-4-5";
9
- readonly SMALL: "claude-sonnet-4-5";
7
+ readonly DEFAULT: "claude-sonnet-4-6";
8
+ readonly LARGE: "claude-opus-4-6";
9
+ readonly SMALL: "claude-sonnet-4-6";
10
10
  readonly TINY: "claude-haiku-4-5";
11
11
  };
12
12
  readonly MODEL_MATCH_WORDS: readonly ["anthropic", "claude", "haiku", "opus", "sonnet"];
@@ -26,10 +26,10 @@ export declare const PROVIDER: {
26
26
  };
27
27
  readonly GEMINI: {
28
28
  readonly MODEL: {
29
- readonly DEFAULT: "gemini-3-pro-preview";
30
- readonly LARGE: "gemini-3-pro-preview";
29
+ readonly DEFAULT: "gemini-3.1-pro-preview";
30
+ readonly LARGE: "gemini-3.1-pro-preview";
31
31
  readonly SMALL: "gemini-3-flash-preview";
32
- readonly TINY: "gemini-3-flash-preview";
32
+ readonly TINY: "gemini-3.1-flash-lite-preview";
33
33
  };
34
34
  readonly MODEL_MATCH_WORDS: readonly ["gemini", "google"];
35
35
  readonly NAME: "google";
@@ -40,20 +40,20 @@ export declare const PROVIDER: {
40
40
  };
41
41
  readonly OPENAI: {
42
42
  readonly MODEL: {
43
- readonly DEFAULT: "gpt-5.2";
44
- readonly LARGE: "gpt-5.2-pro";
45
- readonly SMALL: "gpt-5-mini";
46
- readonly TINY: "gpt-5-nano";
43
+ readonly DEFAULT: "gpt-5.4";
44
+ readonly LARGE: "gpt-5.4";
45
+ readonly SMALL: "gpt-5.4-mini";
46
+ readonly TINY: "gpt-5.4-nano";
47
47
  };
48
48
  readonly MODEL_MATCH_WORDS: readonly ["openai", "gpt", RegExp];
49
49
  readonly NAME: "openai";
50
50
  };
51
51
  readonly OPENROUTER: {
52
52
  readonly MODEL: {
53
- readonly DEFAULT: "z-ai/glm-4.7";
54
- readonly LARGE: "z-ai/glm-4.7";
55
- readonly SMALL: "z-ai/glm-4.7";
56
- readonly TINY: "z-ai/glm-4.7";
53
+ readonly DEFAULT: "anthropic/claude-sonnet-4-6";
54
+ readonly LARGE: "anthropic/claude-opus-4-6";
55
+ readonly SMALL: "anthropic/claude-sonnet-4-6";
56
+ readonly TINY: "anthropic/claude-haiku-4-5";
57
57
  };
58
58
  readonly MODEL_MATCH_WORDS: readonly ["openrouter"];
59
59
  readonly NAME: "openrouter";
@@ -68,10 +68,10 @@ export declare const PROVIDER: {
68
68
  readonly API_KEY: "XAI_API_KEY";
69
69
  readonly BASE_URL: "https://api.x.ai/v1";
70
70
  readonly MODEL: {
71
- readonly DEFAULT: "grok-4-1-fast-reasoning";
72
- readonly LARGE: "grok-4-1-fast-reasoning";
73
- readonly SMALL: "grok-3";
74
- readonly TINY: "grok-3-mini";
71
+ readonly DEFAULT: "grok-4.20-0309-reasoning";
72
+ readonly LARGE: "grok-4.20-0309-reasoning";
73
+ readonly SMALL: "grok-4.20-0309-non-reasoning";
74
+ readonly TINY: "grok-4-1-fast-non-reasoning";
75
75
  };
76
76
  readonly MODEL_MATCH_WORDS: readonly ["grok", "xai"];
77
77
  readonly NAME: "xai";
@@ -80,26 +80,26 @@ export declare const PROVIDER: {
80
80
  export type LlmProviderName = typeof PROVIDER.ANTHROPIC.NAME | typeof PROVIDER.GEMINI.NAME | typeof PROVIDER.OPENAI.NAME | typeof PROVIDER.OPENROUTER.NAME | typeof PROVIDER.XAI.NAME;
81
81
  export declare const DEFAULT: {
82
82
  readonly MODEL: {
83
- readonly BASE: "gpt-5.2";
84
- readonly LARGE: "gpt-5.2-pro";
85
- readonly SMALL: "gpt-5-mini";
86
- readonly TINY: "gpt-5-nano";
83
+ readonly BASE: "gpt-5.4";
84
+ readonly LARGE: "gpt-5.4";
85
+ readonly SMALL: "gpt-5.4-mini";
86
+ readonly TINY: "gpt-5.4-nano";
87
87
  };
88
88
  readonly PROVIDER: {
89
89
  readonly MODEL: {
90
- readonly DEFAULT: "gpt-5.2";
91
- readonly LARGE: "gpt-5.2-pro";
92
- readonly SMALL: "gpt-5-mini";
93
- readonly TINY: "gpt-5-nano";
90
+ readonly DEFAULT: "gpt-5.4";
91
+ readonly LARGE: "gpt-5.4";
92
+ readonly SMALL: "gpt-5.4-mini";
93
+ readonly TINY: "gpt-5.4-nano";
94
94
  };
95
95
  readonly MODEL_MATCH_WORDS: readonly ["openai", "gpt", RegExp];
96
96
  readonly NAME: "openai";
97
97
  };
98
98
  };
99
99
  export declare const ALL: {
100
- readonly BASE: readonly ["claude-sonnet-4-5", "gemini-3-pro-preview", "gpt-5.2", "grok-4-1-fast-reasoning"];
101
- readonly COMBINED: readonly ["claude-sonnet-4-5", "claude-opus-4-5", "claude-sonnet-4-5", "claude-haiku-4-5", "gemini-3-pro-preview", "gemini-3-pro-preview", "gemini-3-flash-preview", "gemini-3-flash-preview", "gpt-5.2", "gpt-5.2-pro", "gpt-5-mini", "gpt-5-nano", "grok-4-1-fast-reasoning", "grok-4-1-fast-reasoning", "grok-3", "grok-3-mini"];
102
- readonly LARGE: readonly ["claude-opus-4-5", "gemini-3-pro-preview", "gpt-5.2-pro", "grok-4-1-fast-reasoning"];
103
- readonly SMALL: readonly ["claude-sonnet-4-5", "gemini-3-flash-preview", "gpt-5-mini", "grok-3"];
104
- readonly TINY: readonly ["claude-haiku-4-5", "gemini-3-flash-preview", "gpt-5-nano", "grok-3-mini"];
100
+ readonly BASE: readonly ["claude-sonnet-4-6", "gemini-3.1-pro-preview", "gpt-5.4", "grok-4.20-0309-reasoning"];
101
+ readonly COMBINED: readonly ["claude-sonnet-4-6", "claude-opus-4-6", "claude-sonnet-4-6", "claude-haiku-4-5", "gemini-3.1-pro-preview", "gemini-3.1-pro-preview", "gemini-3-flash-preview", "gemini-3.1-flash-lite-preview", "gpt-5.4", "gpt-5.4", "gpt-5.4-mini", "gpt-5.4-nano", "grok-4.20-0309-reasoning", "grok-4.20-0309-reasoning", "grok-4.20-0309-non-reasoning", "grok-4-1-fast-non-reasoning"];
102
+ readonly LARGE: readonly ["claude-opus-4-6", "gemini-3.1-pro-preview", "gpt-5.4", "grok-4.20-0309-reasoning"];
103
+ readonly SMALL: readonly ["claude-sonnet-4-6", "gemini-3-flash-preview", "gpt-5.4-mini", "grok-4.20-0309-non-reasoning"];
104
+ readonly TINY: readonly ["claude-haiku-4-5", "gemini-3.1-flash-lite-preview", "gpt-5.4-nano", "grok-4-1-fast-non-reasoning"];
105
105
  };
package/dist/esm/index.js CHANGED
@@ -12,35 +12,33 @@ import { getS3FileBuffer, getEnvSecret } from '@jaypie/aws';
12
12
  import { fetchWeatherApi } from 'openmeteo';
13
13
 
14
14
  const FIRST_CLASS_PROVIDER = {
15
+ // https://docs.anthropic.com/en/docs/about-claude/models/overview
15
16
  ANTHROPIC: {
16
- DEFAULT: "claude-sonnet-4-5",
17
- LARGE: "claude-opus-4-5",
18
- SMALL: "claude-sonnet-4-5",
17
+ DEFAULT: "claude-sonnet-4-6",
18
+ LARGE: "claude-opus-4-6",
19
+ SMALL: "claude-sonnet-4-6",
19
20
  TINY: "claude-haiku-4-5",
20
21
  },
22
+ // https://ai.google.dev/gemini-api/docs/models
21
23
  GEMINI: {
22
- DEFAULT: "gemini-3-pro-preview",
23
- LARGE: "gemini-3-pro-preview",
24
+ DEFAULT: "gemini-3.1-pro-preview",
25
+ LARGE: "gemini-3.1-pro-preview",
24
26
  SMALL: "gemini-3-flash-preview",
25
- TINY: "gemini-3-flash-preview",
27
+ TINY: "gemini-3.1-flash-lite-preview",
26
28
  },
29
+ // https://developers.openai.com/api/docs/models
27
30
  OPENAI: {
28
- DEFAULT: "gpt-5.2",
29
- LARGE: "gpt-5.2-pro",
30
- SMALL: "gpt-5-mini",
31
- TINY: "gpt-5-nano",
32
- },
33
- OPENROUTER: {
34
- DEFAULT: "z-ai/glm-4.7",
35
- LARGE: "z-ai/glm-4.7",
36
- SMALL: "z-ai/glm-4.7",
37
- TINY: "z-ai/glm-4.7",
31
+ DEFAULT: "gpt-5.4",
32
+ LARGE: "gpt-5.4",
33
+ SMALL: "gpt-5.4-mini",
34
+ TINY: "gpt-5.4-nano",
38
35
  },
36
+ // https://docs.x.ai/developers/models
39
37
  XAI: {
40
- DEFAULT: "grok-4-1-fast-reasoning",
41
- LARGE: "grok-4-1-fast-reasoning",
42
- SMALL: "grok-3",
43
- TINY: "grok-3-mini",
38
+ DEFAULT: "grok-4.20-0309-reasoning",
39
+ LARGE: "grok-4.20-0309-reasoning",
40
+ SMALL: "grok-4.20-0309-non-reasoning",
41
+ TINY: "grok-4-1-fast-non-reasoning",
44
42
  },
45
43
  };
46
44
  const PROVIDER = {
@@ -103,15 +101,11 @@ const PROVIDER = {
103
101
  NAME: "openai",
104
102
  },
105
103
  OPENROUTER: {
106
- // https://openrouter.ai/models
107
- // OpenRouter provides access to hundreds of models from various providers
108
- // The model format is: provider/model-name (e.g., "openai/gpt-4", "anthropic/claude-3-opus")
109
104
  MODEL: {
110
- // Default uses env var OPENROUTER_MODEL if set, otherwise a reasonable default
111
- DEFAULT: FIRST_CLASS_PROVIDER.OPENROUTER.DEFAULT,
112
- LARGE: FIRST_CLASS_PROVIDER.OPENROUTER.LARGE,
113
- SMALL: FIRST_CLASS_PROVIDER.OPENROUTER.SMALL,
114
- TINY: FIRST_CLASS_PROVIDER.OPENROUTER.TINY,
105
+ DEFAULT: `anthropic/${FIRST_CLASS_PROVIDER.ANTHROPIC.DEFAULT}`,
106
+ LARGE: `anthropic/${FIRST_CLASS_PROVIDER.ANTHROPIC.LARGE}`,
107
+ SMALL: `anthropic/${FIRST_CLASS_PROVIDER.ANTHROPIC.SMALL}`,
108
+ TINY: `anthropic/${FIRST_CLASS_PROVIDER.ANTHROPIC.TINY}`,
115
109
  },
116
110
  MODEL_MATCH_WORDS: ["openrouter"],
117
111
  NAME: "openrouter",
@@ -561,7 +555,7 @@ const log$1 = getLogger$5();
561
555
 
562
556
  // Turn policy constants
563
557
  const MAX_TURNS_ABSOLUTE_LIMIT = 72;
564
- const MAX_TURNS_DEFAULT_LIMIT = 12;
558
+ const MAX_TURNS_DEFAULT_LIMIT = 24;
565
559
  /**
566
560
  * Determines the maximum number of turns based on the provided options
567
561
  *
@@ -4153,6 +4147,7 @@ class RetryExecutor {
4153
4147
  const ERROR$1 = {
4154
4148
  BAD_FUNCTION_CALL: "Bad Function Call",
4155
4149
  };
4150
+ const MAX_CONSECUTIVE_TOOL_ERRORS = 6;
4156
4151
  //
4157
4152
  //
4158
4153
  // Helpers
@@ -4275,6 +4270,7 @@ class OperateLoop {
4275
4270
  }
4276
4271
  }
4277
4272
  return {
4273
+ consecutiveToolErrors: 0,
4278
4274
  currentInput: processedInput.history,
4279
4275
  currentTurn: 0,
4280
4276
  formattedFormat,
@@ -4399,6 +4395,8 @@ class OperateLoop {
4399
4395
  output: JSON.stringify(result),
4400
4396
  success: true,
4401
4397
  };
4398
+ // Reset consecutive error counter on success
4399
+ state.consecutiveToolErrors = 0;
4402
4400
  // Update provider request with tool result
4403
4401
  currentProviderRequest = this.adapter.appendToolResult(currentProviderRequest, toolCall, formattedResult);
4404
4402
  // Sync state from updated request
@@ -4440,6 +4438,19 @@ class OperateLoop {
4440
4438
  state.responseBuilder.appendToHistory(toolResultFormatted);
4441
4439
  log$1.error(`Error executing function call ${toolCall.name}`);
4442
4440
  log$1.var({ error });
4441
+ // Track consecutive errors and stop if threshold reached
4442
+ state.consecutiveToolErrors++;
4443
+ if (state.consecutiveToolErrors >= MAX_CONSECUTIVE_TOOL_ERRORS) {
4444
+ const detail = `Stopped after ${MAX_CONSECUTIVE_TOOL_ERRORS} consecutive tool errors`;
4445
+ log$1.warn(detail);
4446
+ state.responseBuilder.setError({
4447
+ detail,
4448
+ status: 502,
4449
+ title: ERROR$1.BAD_FUNCTION_CALL,
4450
+ });
4451
+ state.responseBuilder.incomplete();
4452
+ return false; // Stop loop
4453
+ }
4443
4454
  }
4444
4455
  }
4445
4456
  // Check if we've reached max turns
@@ -4687,6 +4698,7 @@ class StreamLoop {
4687
4698
  ? this.adapter.formatTools(toolkit, formattedFormat)
4688
4699
  : undefined;
4689
4700
  return {
4701
+ consecutiveToolErrors: 0,
4690
4702
  currentInput: processedInput.history,
4691
4703
  currentTurn: 0,
4692
4704
  formattedFormat,
@@ -4880,6 +4892,8 @@ class StreamLoop {
4880
4892
  result,
4881
4893
  toolName: toolCall.name,
4882
4894
  });
4895
+ // Reset consecutive error counter on success
4896
+ state.consecutiveToolErrors = 0;
4883
4897
  // Yield tool result chunk
4884
4898
  yield {
4885
4899
  type: LlmStreamChunkType.ToolResult,
@@ -4932,6 +4946,21 @@ class StreamLoop {
4932
4946
  });
4933
4947
  log$1.error(`Error executing function call ${toolCall.name}`);
4934
4948
  log$1.var({ error });
4949
+ // Track consecutive errors and stop if threshold reached
4950
+ state.consecutiveToolErrors++;
4951
+ if (state.consecutiveToolErrors >= MAX_CONSECUTIVE_TOOL_ERRORS) {
4952
+ const stopDetail = `Stopped after ${MAX_CONSECUTIVE_TOOL_ERRORS} consecutive tool errors`;
4953
+ log$1.warn(stopDetail);
4954
+ yield {
4955
+ type: LlmStreamChunkType.Error,
4956
+ error: {
4957
+ detail: stopDetail,
4958
+ status: 502,
4959
+ title: ERROR.BAD_FUNCTION_CALL,
4960
+ },
4961
+ };
4962
+ return; // Stop processing tools
4963
+ }
4935
4964
  }
4936
4965
  }
4937
4966
  }