@jaypie/llm 1.2.5 → 1.2.7

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/Llm.d.ts CHANGED
@@ -1,14 +1,25 @@
1
1
  import { JsonObject } from "@jaypie/types";
2
2
  import { LlmProviderName } from "./constants.js";
3
- import { LlmHistory, LlmInputMessage, LlmMessageOptions, LlmOperateInput, LlmOperateOptions, LlmOperateResponse, LlmOptions, LlmProvider } from "./types/LlmProvider.interface.js";
3
+ import { LlmFallbackConfig, LlmHistory, LlmInputMessage, LlmMessageOptions, LlmOperateInput, LlmOperateOptions, LlmOperateResponse, LlmOptions, LlmProvider } from "./types/LlmProvider.interface.js";
4
4
  import { LlmStreamChunk } from "./types/LlmStreamChunk.interface.js";
5
5
  declare class Llm implements LlmProvider {
6
- private _provider;
6
+ private _fallbackConfig?;
7
7
  private _llm;
8
8
  private _options;
9
+ private _provider;
9
10
  constructor(providerName?: LlmProviderName | string, options?: LlmOptions);
10
11
  private createProvider;
11
12
  send(message: string, options?: LlmMessageOptions): Promise<string | JsonObject>;
13
+ /**
14
+ * Resolves the fallback chain from instance config and per-call options.
15
+ * Per-call options take precedence over instance config.
16
+ * Returns empty array if fallback is disabled.
17
+ */
18
+ private resolveFallbackChain;
19
+ /**
20
+ * Creates a fallback Llm instance lazily when needed.
21
+ */
22
+ private createFallbackInstance;
12
23
  operate(input: string | LlmHistory | LlmInputMessage | LlmOperateInput, options?: LlmOperateOptions): Promise<LlmOperateResponse>;
13
24
  stream(input: string | LlmHistory | LlmInputMessage | LlmOperateInput, options?: LlmOperateOptions): AsyncIterable<LlmStreamChunk>;
14
25
  static send(message: string, options?: LlmMessageOptions & {
@@ -17,8 +28,9 @@ declare class Llm implements LlmProvider {
17
28
  model?: string;
18
29
  }): Promise<string | JsonObject>;
19
30
  static operate(input: string | LlmHistory | LlmInputMessage | LlmOperateInput, options?: LlmOperateOptions & {
20
- llm?: LlmProviderName;
21
31
  apiKey?: string;
32
+ fallback?: LlmFallbackConfig[] | false;
33
+ llm?: LlmProviderName;
22
34
  model?: string;
23
35
  }): Promise<LlmOperateResponse>;
24
36
  static stream(input: string | LlmHistory | LlmInputMessage | LlmOperateInput, options?: LlmOperateOptions & {
@@ -1,19 +1,28 @@
1
1
  export declare const PROVIDER: {
2
- readonly OPENROUTER: {
2
+ readonly ANTHROPIC: {
3
+ readonly MAX_TOKENS: {
4
+ readonly DEFAULT: 4096;
5
+ };
3
6
  readonly MODEL: {
4
- readonly DEFAULT: "z-ai/glm-4.7";
5
- readonly SMALL: "z-ai/glm-4.7";
6
- readonly LARGE: "z-ai/glm-4.7";
7
- readonly TINY: "z-ai/glm-4.7";
7
+ readonly DEFAULT: "claude-sonnet-4-5";
8
+ readonly LARGE: "claude-opus-4-5";
9
+ readonly SMALL: "claude-sonnet-4-5";
10
+ readonly TINY: "claude-haiku-4-5";
11
+ };
12
+ readonly MODEL_MATCH_WORDS: readonly ["anthropic", "claude", "haiku", "opus", "sonnet"];
13
+ readonly NAME: "anthropic";
14
+ readonly PROMPT: {
15
+ readonly AI: "\n\nAssistant:";
16
+ readonly HUMAN: "\n\nHuman:";
8
17
  };
9
- readonly MODEL_MATCH_WORDS: readonly ["openrouter"];
10
- readonly NAME: "openrouter";
11
18
  readonly ROLE: {
12
19
  readonly ASSISTANT: "assistant";
13
20
  readonly SYSTEM: "system";
14
- readonly TOOL: "tool";
15
21
  readonly USER: "user";
16
22
  };
23
+ readonly TOOLS: {
24
+ readonly SCHEMA_VERSION: "v2";
25
+ };
17
26
  };
18
27
  readonly GEMINI: {
19
28
  readonly MODEL: {
@@ -29,65 +38,56 @@ export declare const PROVIDER: {
29
38
  readonly USER: "user";
30
39
  };
31
40
  };
32
- readonly ANTHROPIC: {
41
+ readonly OPENAI: {
33
42
  readonly MODEL: {
34
- readonly DEFAULT: "claude-sonnet-4-5";
35
- readonly LARGE: "claude-opus-4-5";
36
- readonly SMALL: "claude-sonnet-4-5";
37
- readonly TINY: "claude-haiku-4-5";
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";
38
47
  };
39
- readonly MODEL_MATCH_WORDS: readonly ["anthropic", "claude", "haiku", "opus", "sonnet"];
40
- readonly NAME: "anthropic";
41
- readonly PROMPT: {
42
- readonly AI: "\n\nAssistant:";
43
- readonly HUMAN: "\n\nHuman:";
48
+ readonly MODEL_MATCH_WORDS: readonly ["openai", "gpt", RegExp];
49
+ readonly NAME: "openai";
50
+ };
51
+ readonly OPENROUTER: {
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";
44
57
  };
58
+ readonly MODEL_MATCH_WORDS: readonly ["openrouter"];
59
+ readonly NAME: "openrouter";
45
60
  readonly ROLE: {
46
61
  readonly ASSISTANT: "assistant";
47
62
  readonly SYSTEM: "system";
63
+ readonly TOOL: "tool";
48
64
  readonly USER: "user";
49
65
  };
50
- readonly MAX_TOKENS: {
51
- readonly DEFAULT: 4096;
52
- };
53
- readonly TOOLS: {
54
- readonly SCHEMA_VERSION: "v2";
55
- };
56
- };
57
- readonly OPENAI: {
58
- readonly MODEL: {
59
- readonly DEFAULT: "gpt-5.1";
60
- readonly LARGE: "gpt-5.1";
61
- readonly SMALL: "gpt-5.1-mini";
62
- readonly TINY: "gpt-5.1-nano";
63
- };
64
- readonly MODEL_MATCH_WORDS: readonly ["openai", "gpt", RegExp];
65
- readonly NAME: "openai";
66
66
  };
67
67
  };
68
68
  export type LlmProviderName = typeof PROVIDER.ANTHROPIC.NAME | typeof PROVIDER.GEMINI.NAME | typeof PROVIDER.OPENAI.NAME | typeof PROVIDER.OPENROUTER.NAME;
69
69
  export declare const DEFAULT: {
70
70
  readonly MODEL: {
71
- readonly BASE: "gpt-5.1";
72
- readonly LARGE: "gpt-5.1";
73
- readonly SMALL: "gpt-5.1-mini";
74
- readonly TINY: "gpt-5.1-nano";
71
+ readonly BASE: "gpt-5.2";
72
+ readonly LARGE: "gpt-5.2-pro";
73
+ readonly SMALL: "gpt-5-mini";
74
+ readonly TINY: "gpt-5-nano";
75
75
  };
76
76
  readonly PROVIDER: {
77
77
  readonly MODEL: {
78
- readonly DEFAULT: "gpt-5.1";
79
- readonly LARGE: "gpt-5.1";
80
- readonly SMALL: "gpt-5.1-mini";
81
- readonly TINY: "gpt-5.1-nano";
78
+ readonly DEFAULT: "gpt-5.2";
79
+ readonly LARGE: "gpt-5.2-pro";
80
+ readonly SMALL: "gpt-5-mini";
81
+ readonly TINY: "gpt-5-nano";
82
82
  };
83
83
  readonly MODEL_MATCH_WORDS: readonly ["openai", "gpt", RegExp];
84
84
  readonly NAME: "openai";
85
85
  };
86
86
  };
87
87
  export declare const ALL: {
88
- readonly BASE: readonly ["claude-sonnet-4-5", "gemini-3-pro-preview", "gpt-5.1"];
89
- 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.1", "gpt-5.1", "gpt-5.1-mini", "gpt-5.1-nano"];
90
- readonly LARGE: readonly ["claude-opus-4-5", "gemini-3-pro-preview", "gpt-5.1"];
91
- readonly SMALL: readonly ["claude-sonnet-4-5", "gemini-3-flash-preview", "gpt-5.1-mini"];
92
- readonly TINY: readonly ["claude-haiku-4-5", "gemini-3-flash-preview", "gpt-5.1-nano"];
88
+ readonly BASE: readonly ["claude-sonnet-4-5", "gemini-3-pro-preview", "gpt-5.2"];
89
+ 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"];
90
+ readonly LARGE: readonly ["claude-opus-4-5", "gemini-3-pro-preview", "gpt-5.2-pro"];
91
+ readonly SMALL: readonly ["claude-sonnet-4-5", "gemini-3-flash-preview", "gpt-5-mini"];
92
+ readonly TINY: readonly ["claude-haiku-4-5", "gemini-3-flash-preview", "gpt-5-nano"];
93
93
  };
@@ -1,9 +1,9 @@
1
1
  'use strict';
2
2
 
3
3
  var errors = require('@jaypie/errors');
4
+ var log$2 = require('@jaypie/logger');
4
5
  var v4 = require('zod/v4');
5
6
  var kit = require('@jaypie/kit');
6
- var logger = require('@jaypie/logger');
7
7
  var RandomLib = require('random');
8
8
  var openai = require('openai');
9
9
  var zod = require('openai/helpers/zod');
@@ -13,49 +13,43 @@ var path = require('path');
13
13
  var aws = require('@jaypie/aws');
14
14
  var openmeteo = require('openmeteo');
15
15
 
16
- const PROVIDER = {
17
- OPENROUTER: {
18
- // https://openrouter.ai/models
19
- // OpenRouter provides access to hundreds of models from various providers
20
- // The model format is: provider/model-name (e.g., "openai/gpt-4", "anthropic/claude-3-opus")
21
- MODEL: {
22
- // Default uses env var OPENROUTER_MODEL if set, otherwise a reasonable default
23
- DEFAULT: "z-ai/glm-4.7",
24
- SMALL: "z-ai/glm-4.7",
25
- LARGE: "z-ai/glm-4.7",
26
- TINY: "z-ai/glm-4.7",
27
- },
28
- MODEL_MATCH_WORDS: ["openrouter"],
29
- NAME: "openrouter",
30
- ROLE: {
31
- ASSISTANT: "assistant",
32
- SYSTEM: "system",
33
- TOOL: "tool",
34
- USER: "user",
35
- },
16
+ const FIRST_CLASS_PROVIDER = {
17
+ ANTHROPIC: {
18
+ DEFAULT: "claude-sonnet-4-5",
19
+ LARGE: "claude-opus-4-5",
20
+ SMALL: "claude-sonnet-4-5",
21
+ TINY: "claude-haiku-4-5",
36
22
  },
37
23
  GEMINI: {
38
- // https://ai.google.dev/gemini-api/docs/models
39
- MODEL: {
40
- DEFAULT: "gemini-3-pro-preview",
41
- LARGE: "gemini-3-pro-preview",
42
- SMALL: "gemini-3-flash-preview",
43
- TINY: "gemini-3-flash-preview",
44
- },
45
- MODEL_MATCH_WORDS: ["gemini", "google"],
46
- NAME: "gemini",
47
- ROLE: {
48
- MODEL: "model",
49
- USER: "user",
50
- },
24
+ DEFAULT: "gemini-3-pro-preview",
25
+ LARGE: "gemini-3-pro-preview",
26
+ SMALL: "gemini-3-flash-preview",
27
+ TINY: "gemini-3-flash-preview",
28
+ },
29
+ OPENAI: {
30
+ DEFAULT: "gpt-5.2",
31
+ LARGE: "gpt-5.2-pro",
32
+ SMALL: "gpt-5-mini",
33
+ TINY: "gpt-5-nano",
34
+ },
35
+ OPENROUTER: {
36
+ DEFAULT: "z-ai/glm-4.7",
37
+ LARGE: "z-ai/glm-4.7",
38
+ SMALL: "z-ai/glm-4.7",
39
+ TINY: "z-ai/glm-4.7",
51
40
  },
41
+ };
42
+ const PROVIDER = {
52
43
  ANTHROPIC: {
53
44
  // https://docs.anthropic.com/en/docs/about-claude/models/overview
45
+ MAX_TOKENS: {
46
+ DEFAULT: 4096,
47
+ },
54
48
  MODEL: {
55
- DEFAULT: "claude-sonnet-4-5",
56
- LARGE: "claude-opus-4-5",
57
- SMALL: "claude-sonnet-4-5",
58
- TINY: "claude-haiku-4-5",
49
+ DEFAULT: FIRST_CLASS_PROVIDER.ANTHROPIC.DEFAULT,
50
+ LARGE: FIRST_CLASS_PROVIDER.ANTHROPIC.LARGE,
51
+ SMALL: FIRST_CLASS_PROVIDER.ANTHROPIC.SMALL,
52
+ TINY: FIRST_CLASS_PROVIDER.ANTHROPIC.TINY,
59
53
  },
60
54
  MODEL_MATCH_WORDS: [
61
55
  "anthropic",
@@ -74,24 +68,56 @@ const PROVIDER = {
74
68
  SYSTEM: "system",
75
69
  USER: "user",
76
70
  },
77
- MAX_TOKENS: {
78
- DEFAULT: 4096,
79
- },
80
71
  TOOLS: {
81
72
  SCHEMA_VERSION: "v2",
82
73
  },
83
74
  },
75
+ GEMINI: {
76
+ // https://ai.google.dev/gemini-api/docs/models
77
+ MODEL: {
78
+ DEFAULT: FIRST_CLASS_PROVIDER.GEMINI.DEFAULT,
79
+ LARGE: FIRST_CLASS_PROVIDER.GEMINI.LARGE,
80
+ SMALL: FIRST_CLASS_PROVIDER.GEMINI.SMALL,
81
+ TINY: FIRST_CLASS_PROVIDER.GEMINI.TINY,
82
+ },
83
+ MODEL_MATCH_WORDS: ["gemini", "google"],
84
+ NAME: "gemini",
85
+ ROLE: {
86
+ MODEL: "model",
87
+ USER: "user",
88
+ },
89
+ },
84
90
  OPENAI: {
85
91
  // https://platform.openai.com/docs/models
86
92
  MODEL: {
87
- DEFAULT: "gpt-5.1",
88
- LARGE: "gpt-5.1",
89
- SMALL: "gpt-5.1-mini",
90
- TINY: "gpt-5.1-nano",
93
+ DEFAULT: FIRST_CLASS_PROVIDER.OPENAI.DEFAULT,
94
+ LARGE: FIRST_CLASS_PROVIDER.OPENAI.LARGE,
95
+ SMALL: FIRST_CLASS_PROVIDER.OPENAI.SMALL,
96
+ TINY: FIRST_CLASS_PROVIDER.OPENAI.TINY,
91
97
  },
92
98
  MODEL_MATCH_WORDS: ["openai", "gpt", /^o\d/],
93
99
  NAME: "openai",
94
100
  },
101
+ OPENROUTER: {
102
+ // https://openrouter.ai/models
103
+ // OpenRouter provides access to hundreds of models from various providers
104
+ // The model format is: provider/model-name (e.g., "openai/gpt-4", "anthropic/claude-3-opus")
105
+ MODEL: {
106
+ // Default uses env var OPENROUTER_MODEL if set, otherwise a reasonable default
107
+ DEFAULT: FIRST_CLASS_PROVIDER.OPENROUTER.DEFAULT,
108
+ LARGE: FIRST_CLASS_PROVIDER.OPENROUTER.LARGE,
109
+ SMALL: FIRST_CLASS_PROVIDER.OPENROUTER.SMALL,
110
+ TINY: FIRST_CLASS_PROVIDER.OPENROUTER.TINY,
111
+ },
112
+ MODEL_MATCH_WORDS: ["openrouter"],
113
+ NAME: "openrouter",
114
+ ROLE: {
115
+ ASSISTANT: "assistant",
116
+ SYSTEM: "system",
117
+ TOOL: "tool",
118
+ USER: "user",
119
+ },
120
+ },
95
121
  };
96
122
  // Last: Defaults
97
123
  const DEFAULT = {
@@ -481,7 +507,7 @@ function formatOperateInput(input, options) {
481
507
  return [input];
482
508
  }
483
509
 
484
- const getLogger$4 = () => logger.log.lib({ lib: kit.JAYPIE.LIB.LLM });
510
+ const getLogger$4 = () => log$2.log.lib({ lib: kit.JAYPIE.LIB.LLM });
485
511
  const log$1 = getLogger$4();
486
512
 
487
513
  // Turn policy constants
@@ -2368,16 +2394,16 @@ function convertContentToOpenRouter(content) {
2368
2394
  }
2369
2395
  // Image content - warn and discard
2370
2396
  if (item.type === exports.LlmMessageType.InputImage) {
2371
- logger.log.warn("OpenRouter does not support image uploads; image discarded");
2397
+ log$2.log.warn("OpenRouter does not support image uploads; image discarded");
2372
2398
  continue;
2373
2399
  }
2374
2400
  // File/Document content - warn and discard
2375
2401
  if (item.type === exports.LlmMessageType.InputFile) {
2376
- logger.log.warn({ filename: item.filename }, "OpenRouter does not support file uploads; file discarded");
2402
+ log$2.log.warn({ filename: item.filename }, "OpenRouter does not support file uploads; file discarded");
2377
2403
  continue;
2378
2404
  }
2379
2405
  // Unknown type - warn and skip
2380
- logger.log.warn({ item }, "Unknown content type for OpenRouter; discarded");
2406
+ log$2.log.warn({ item }, "Unknown content type for OpenRouter; discarded");
2381
2407
  }
2382
2408
  // If no text parts remain, return empty string to avoid empty array
2383
2409
  if (parts.length === 0) {
@@ -2893,7 +2919,7 @@ class OpenRouterAdapter extends BaseProviderAdapter {
2893
2919
  const openRouterAdapter = new OpenRouterAdapter();
2894
2920
 
2895
2921
  const DEFAULT_TOOL_TYPE = "function";
2896
- const log = logger.log.lib({ lib: kit.JAYPIE.LIB.LLM });
2922
+ const log = log$2.log.lib({ lib: kit.JAYPIE.LIB.LLM });
2897
2923
  function logToolMessage(message, context) {
2898
2924
  log.trace.var({ [context.name]: message });
2899
2925
  }
@@ -4495,7 +4521,7 @@ async function loadSdk$2() {
4495
4521
  }
4496
4522
  }
4497
4523
  // Logger
4498
- const getLogger$3 = () => logger.log.lib({ lib: kit.JAYPIE.LIB.LLM });
4524
+ const getLogger$3 = () => log$2.log.lib({ lib: kit.JAYPIE.LIB.LLM });
4499
4525
  // Client initialization
4500
4526
  async function initializeClient$3({ apiKey, } = {}) {
4501
4527
  const logger = getLogger$3();
@@ -4534,7 +4560,7 @@ function prepareMessages$3(message, { data, placeholders } = {}) {
4534
4560
  }
4535
4561
  // Basic text completion
4536
4562
  async function createTextCompletion$1(client, messages, model, systemMessage) {
4537
- logger.log.trace("Using text output (unstructured)");
4563
+ log$2.log.trace("Using text output (unstructured)");
4538
4564
  const params = {
4539
4565
  model,
4540
4566
  messages,
@@ -4543,17 +4569,17 @@ async function createTextCompletion$1(client, messages, model, systemMessage) {
4543
4569
  // Add system instruction if provided
4544
4570
  if (systemMessage) {
4545
4571
  params.system = systemMessage;
4546
- logger.log.trace(`System message: ${systemMessage.length} characters`);
4572
+ log$2.log.trace(`System message: ${systemMessage.length} characters`);
4547
4573
  }
4548
4574
  const response = await client.messages.create(params);
4549
4575
  const firstContent = response.content[0];
4550
4576
  const text = firstContent && "text" in firstContent ? firstContent.text : "";
4551
- logger.log.trace(`Assistant reply: ${text.length} characters`);
4577
+ log$2.log.trace(`Assistant reply: ${text.length} characters`);
4552
4578
  return text;
4553
4579
  }
4554
4580
  // Structured output completion
4555
4581
  async function createStructuredCompletion$1(client, messages, model, responseSchema, systemMessage) {
4556
- logger.log.trace("Using structured output");
4582
+ log$2.log.trace("Using structured output");
4557
4583
  // Get the JSON schema for the response
4558
4584
  const schema = responseSchema instanceof v4.z.ZodType
4559
4585
  ? responseSchema
@@ -4586,7 +4612,7 @@ async function createStructuredCompletion$1(client, messages, model, responseSch
4586
4612
  if (!schema.parse(result)) {
4587
4613
  throw new Error(`JSON response from Anthropic does not match schema: ${responseText}`);
4588
4614
  }
4589
- logger.log.trace("Received structured response", { result });
4615
+ log$2.log.trace("Received structured response", { result });
4590
4616
  return result;
4591
4617
  }
4592
4618
  catch {
@@ -4597,7 +4623,7 @@ async function createStructuredCompletion$1(client, messages, model, responseSch
4597
4623
  throw new Error("Failed to parse structured response from Anthropic");
4598
4624
  }
4599
4625
  catch (error) {
4600
- logger.log.error("Error creating structured completion", { error });
4626
+ log$2.log.error("Error creating structured completion", { error });
4601
4627
  throw error;
4602
4628
  }
4603
4629
  }
@@ -4711,7 +4737,7 @@ async function loadSdk$1() {
4711
4737
  }
4712
4738
  }
4713
4739
  // Logger
4714
- const getLogger$2 = () => logger.log.lib({ lib: kit.JAYPIE.LIB.LLM });
4740
+ const getLogger$2 = () => log$2.log.lib({ lib: kit.JAYPIE.LIB.LLM });
4715
4741
  // Client initialization
4716
4742
  async function initializeClient$2({ apiKey, } = {}) {
4717
4743
  const logger = getLogger$2();
@@ -4851,7 +4877,7 @@ class GeminiProvider {
4851
4877
  }
4852
4878
 
4853
4879
  // Logger
4854
- const getLogger$1 = () => logger.log.lib({ lib: kit.JAYPIE.LIB.LLM });
4880
+ const getLogger$1 = () => log$2.log.lib({ lib: kit.JAYPIE.LIB.LLM });
4855
4881
  // Client initialization
4856
4882
  async function initializeClient$1({ apiKey, } = {}) {
4857
4883
  const logger = getLogger$1();
@@ -5035,7 +5061,7 @@ async function loadSdk() {
5035
5061
  }
5036
5062
  }
5037
5063
  // Logger
5038
- const getLogger = () => logger.log.lib({ lib: kit.JAYPIE.LIB.LLM });
5064
+ const getLogger = () => log$2.log.lib({ lib: kit.JAYPIE.LIB.LLM });
5039
5065
  // Client initialization
5040
5066
  async function initializeClient({ apiKey, } = {}) {
5041
5067
  const logger = getLogger();
@@ -5185,7 +5211,7 @@ class OpenRouterProvider {
5185
5211
 
5186
5212
  class Llm {
5187
5213
  constructor(providerName = DEFAULT.PROVIDER.NAME, options = {}) {
5188
- const { model } = options;
5214
+ const { fallback, model } = options;
5189
5215
  let finalProvider = providerName;
5190
5216
  let finalModel = model;
5191
5217
  if (model) {
@@ -5214,6 +5240,7 @@ class Llm {
5214
5240
  finalModel = undefined;
5215
5241
  }
5216
5242
  }
5243
+ this._fallbackConfig = fallback;
5217
5244
  this._provider = finalProvider;
5218
5245
  this._options = { ...options, model: finalModel };
5219
5246
  this._llm = this.createProvider(finalProvider, this._options);
@@ -5242,11 +5269,81 @@ class Llm {
5242
5269
  async send(message, options) {
5243
5270
  return this._llm.send(message, options);
5244
5271
  }
5272
+ /**
5273
+ * Resolves the fallback chain from instance config and per-call options.
5274
+ * Per-call options take precedence over instance config.
5275
+ * Returns empty array if fallback is disabled.
5276
+ */
5277
+ resolveFallbackChain(options) {
5278
+ // Per-call `fallback: false` disables fallback entirely
5279
+ if (options.fallback === false) {
5280
+ return [];
5281
+ }
5282
+ // Per-call fallback array overrides instance config
5283
+ if (Array.isArray(options.fallback)) {
5284
+ return options.fallback;
5285
+ }
5286
+ // Use instance config if available
5287
+ return this._fallbackConfig || [];
5288
+ }
5289
+ /**
5290
+ * Creates a fallback Llm instance lazily when needed.
5291
+ */
5292
+ createFallbackInstance(config) {
5293
+ return new Llm(config.provider, {
5294
+ apiKey: config.apiKey,
5295
+ model: config.model,
5296
+ });
5297
+ }
5245
5298
  async operate(input, options = {}) {
5246
5299
  if (!this._llm.operate) {
5247
5300
  throw new errors.NotImplementedError(`Provider ${this._provider} does not support operate method`);
5248
5301
  }
5249
- return this._llm.operate(input, options);
5302
+ const fallbackChain = this.resolveFallbackChain(options);
5303
+ const optionsWithoutFallback = { ...options, fallback: false };
5304
+ let lastError;
5305
+ let attempts = 0;
5306
+ // Try primary provider first
5307
+ attempts++;
5308
+ try {
5309
+ const response = await this._llm.operate(input, optionsWithoutFallback);
5310
+ return {
5311
+ ...response,
5312
+ fallbackAttempts: attempts,
5313
+ fallbackUsed: false,
5314
+ provider: response.provider || this._provider,
5315
+ };
5316
+ }
5317
+ catch (error) {
5318
+ lastError = error;
5319
+ log$2.warn(`Provider ${this._provider} failed`, {
5320
+ error: lastError.message,
5321
+ fallbacksRemaining: fallbackChain.length,
5322
+ });
5323
+ }
5324
+ // Try fallback providers
5325
+ for (const fallbackConfig of fallbackChain) {
5326
+ attempts++;
5327
+ try {
5328
+ const fallbackInstance = this.createFallbackInstance(fallbackConfig);
5329
+ const response = await fallbackInstance.operate(input, optionsWithoutFallback);
5330
+ return {
5331
+ ...response,
5332
+ fallbackAttempts: attempts,
5333
+ fallbackUsed: true,
5334
+ provider: response.provider || fallbackConfig.provider,
5335
+ };
5336
+ }
5337
+ catch (error) {
5338
+ lastError = error;
5339
+ log$2.warn(`Fallback provider ${fallbackConfig.provider} failed`, {
5340
+ error: lastError.message,
5341
+ fallbacksRemaining: fallbackChain.length - attempts + 1,
5342
+ });
5343
+ }
5344
+ }
5345
+ // All providers failed, throw the last error
5346
+ throw lastError;
5250
5347
  }
5251
5348
  async *stream(input, options = {}) {
5252
5349
  if (!this._llm.stream) {
@@ -5260,7 +5357,7 @@ class Llm {
5260
5357
  return instance.send(message, messageOptions);
5261
5358
  }
5262
5359
  static async operate(input, options) {
5263
- const { llm, apiKey, model, ...operateOptions } = options || {};
5360
+ const { apiKey, fallback, llm, model, ...operateOptions } = options || {};
5264
5361
  let finalLlm = llm;
5265
5362
  let finalModel = model;
5266
5363
  if (!llm && model) {
@@ -5277,8 +5374,18 @@ class Llm {
5277
5374
  finalModel = undefined;
5278
5375
  }
5279
5376
  }
5280
- const instance = new Llm(finalLlm, { apiKey, model: finalModel });
5281
- return instance.operate(input, operateOptions);
5377
+ // Resolve fallback for static method: pass to instance if array, pass to operate options if false
5378
+ const instanceFallback = Array.isArray(fallback) ? fallback : undefined;
5379
+ const operateFallback = fallback === false ? false : undefined;
5380
+ const instance = new Llm(finalLlm, {
5381
+ apiKey,
5382
+ fallback: instanceFallback,
5383
+ model: finalModel,
5384
+ });
5385
+ return instance.operate(input, {
5386
+ ...operateOptions,
5387
+ ...(operateFallback !== undefined && { fallback: operateFallback }),
5388
+ });
5282
5389
  }
5283
5390
  static stream(input, options) {
5284
5391
  const { llm, apiKey, model, ...streamOptions } = options || {};