@jaypie/llm 1.3.9 → 1.3.11

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 (42) hide show
  1. package/dist/cjs/Llm.d.ts +19 -11
  2. package/dist/cjs/constants.d.ts +53 -4
  3. package/dist/cjs/errors/LlmError.d.ts +62 -0
  4. package/dist/cjs/errors/toLlmError.d.ts +11 -0
  5. package/dist/cjs/index.cjs +739 -177
  6. package/dist/cjs/index.cjs.map +1 -1
  7. package/dist/cjs/index.d.cts +177 -19
  8. package/dist/cjs/index.d.ts +5 -1
  9. package/dist/cjs/operate/adapters/AnthropicAdapter.d.ts +3 -2
  10. package/dist/cjs/operate/adapters/BedrockAdapter.d.ts +1 -1
  11. package/dist/cjs/operate/adapters/GoogleAdapter.d.ts +1 -1
  12. package/dist/cjs/operate/adapters/OpenAiAdapter.d.ts +7 -1
  13. package/dist/cjs/operate/adapters/OpenRouterAdapter.d.ts +5 -1
  14. package/dist/cjs/operate/adapters/XaiAdapter.d.ts +11 -1
  15. package/dist/cjs/operate/retry/RetryExecutor.d.ts +13 -0
  16. package/dist/cjs/operate/types.d.ts +15 -1
  17. package/dist/cjs/providers/google/types.d.ts +5 -0
  18. package/dist/cjs/types/LlmProvider.interface.d.ts +18 -0
  19. package/dist/cjs/util/classifyProviderError.d.ts +13 -0
  20. package/dist/cjs/util/effort.d.ts +42 -0
  21. package/dist/cjs/util/resolveModelChain.d.ts +17 -0
  22. package/dist/esm/Llm.d.ts +19 -11
  23. package/dist/esm/constants.d.ts +53 -4
  24. package/dist/esm/errors/LlmError.d.ts +62 -0
  25. package/dist/esm/errors/toLlmError.d.ts +11 -0
  26. package/dist/esm/index.d.ts +177 -19
  27. package/dist/esm/index.js +705 -148
  28. package/dist/esm/index.js.map +1 -1
  29. package/dist/esm/operate/adapters/AnthropicAdapter.d.ts +3 -2
  30. package/dist/esm/operate/adapters/BedrockAdapter.d.ts +1 -1
  31. package/dist/esm/operate/adapters/GoogleAdapter.d.ts +1 -1
  32. package/dist/esm/operate/adapters/OpenAiAdapter.d.ts +7 -1
  33. package/dist/esm/operate/adapters/OpenRouterAdapter.d.ts +5 -1
  34. package/dist/esm/operate/adapters/XaiAdapter.d.ts +11 -1
  35. package/dist/esm/operate/retry/RetryExecutor.d.ts +13 -0
  36. package/dist/esm/operate/types.d.ts +15 -1
  37. package/dist/esm/providers/google/types.d.ts +5 -0
  38. package/dist/esm/types/LlmProvider.interface.d.ts +18 -0
  39. package/dist/esm/util/classifyProviderError.d.ts +13 -0
  40. package/dist/esm/util/effort.d.ts +42 -0
  41. package/dist/esm/util/resolveModelChain.d.ts +17 -0
  42. package/package.json +1 -1
@@ -14,35 +14,21 @@ var aws = require('@jaypie/aws');
14
14
  var openmeteo = require('openmeteo');
15
15
 
16
16
  var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null;
17
- const FIRST_CLASS_PROVIDER = {
18
- // https://docs.anthropic.com/en/docs/about-claude/models/overview
19
- ANTHROPIC: {
20
- DEFAULT: "claude-sonnet-4-6",
21
- LARGE: "claude-opus-4-8",
22
- SMALL: "claude-sonnet-4-6",
23
- TINY: "claude-haiku-4-5",
24
- },
25
- // https://ai.google.dev/gemini-api/docs/models
26
- GOOGLE: {
27
- DEFAULT: "gemini-3.1-pro-preview",
28
- LARGE: "gemini-3.1-pro-preview",
29
- SMALL: "gemini-3.5-flash",
30
- TINY: "gemini-3.1-flash-lite",
31
- },
32
- // https://developers.openai.com/api/docs/models
33
- OPENAI: {
34
- DEFAULT: "gpt-5.4",
35
- LARGE: "gpt-5.5",
36
- SMALL: "gpt-5.4-mini",
37
- TINY: "gpt-5.4-nano",
38
- },
39
- // https://docs.x.ai/developers/models
40
- XAI: {
41
- DEFAULT: "grok-latest",
42
- LARGE: "grok-latest",
43
- SMALL: "grok-4-1-fast-reasoning",
44
- TINY: "grok-4-1-fast-non-reasoning",
45
- },
17
+ /**
18
+ * Provider-neutral reasoning-effort levels — a five-point relative scale that
19
+ * deliberately borrows no provider's vocabulary. Each adapter translates these
20
+ * to its provider's native control (OpenAI `reasoning.effort`, Anthropic
21
+ * `output_config.effort`, Gemini `thinkingLevel`/`thinkingBudget`, Grok
22
+ * `reasoning_effort`, OpenRouter `reasoning.effort`), spreading the scale across
23
+ * the provider's available range. Omitting `effort` leaves the provider default
24
+ * untouched, so it is safe to set across a fallback chain.
25
+ */
26
+ const EFFORT = {
27
+ LOWEST: "lowest",
28
+ LOW: "low",
29
+ MEDIUM: "medium",
30
+ HIGH: "high",
31
+ HIGHEST: "highest",
46
32
  };
47
33
  const MODEL = {
48
34
  // Anthropic
@@ -56,19 +42,33 @@ const MODEL = {
56
42
  GEMINI_FLASH_LITE: "gemini-3.1-flash-lite",
57
43
  GEMINI_PRO: "gemini-3.1-pro-preview",
58
44
  // OpenAI
45
+ SOL: "gpt-5.6-sol",
46
+ TERRA: "gpt-5.6-terra",
47
+ LUNA: "gpt-5.6-luna",
48
+ /** @deprecated use MODEL.SOL (gpt-5.6-sol) */
59
49
  GPT: "gpt-5.5",
50
+ /** @deprecated use MODEL.TERRA (gpt-5.6-terra) */
60
51
  GPT_MINI: "gpt-5.4-mini",
52
+ /** @deprecated use MODEL.LUNA (gpt-5.6-luna) */
61
53
  GPT_NANO: "gpt-5.4-nano",
62
54
  // xAI
63
55
  GROK: "grok-latest",
56
+ // OpenRouter (provider-prefixed routes; traversed by the OpenRouter hot test)
57
+ OPENROUTER: {
58
+ GLM: "z-ai/glm-5.2",
59
+ LUNA: "openai/gpt-5.6-luna",
60
+ SONNET: "anthropic/claude-sonnet-5",
61
+ },
64
62
  };
65
63
  const GOOGLE_PROVIDER = {
66
64
  // https://ai.google.dev/gemini-api/docs/models
65
+ DEFAULT: MODEL.GEMINI_FLASH,
66
+ /** @deprecated Size tiers are retired in 2.0. Use PROVIDER.GOOGLE.DEFAULT, or pick a specific model from MODEL.*. */
67
67
  MODEL: {
68
- DEFAULT: FIRST_CLASS_PROVIDER.GOOGLE.DEFAULT,
69
- LARGE: FIRST_CLASS_PROVIDER.GOOGLE.LARGE,
70
- SMALL: FIRST_CLASS_PROVIDER.GOOGLE.SMALL,
71
- TINY: FIRST_CLASS_PROVIDER.GOOGLE.TINY,
68
+ DEFAULT: "gemini-3.1-pro-preview",
69
+ LARGE: "gemini-3.1-pro-preview",
70
+ SMALL: "gemini-3.5-flash",
71
+ TINY: "gemini-3.1-flash-lite",
72
72
  },
73
73
  MODEL_MATCH_WORDS: ["gemini", "google"],
74
74
  NAME: "google",
@@ -80,6 +80,10 @@ const GOOGLE_PROVIDER = {
80
80
  const PROVIDER = {
81
81
  // https://docs.aws.amazon.com/bedrock/latest/userguide/models-supported.html
82
82
  BEDROCK: {
83
+ // Bedrock has no MODEL.* catalog entry yet; keep the literal default id.
84
+ // nova-pro is the Amazon-native model that reliably does tools+structured.
85
+ DEFAULT: "amazon.nova-pro-v1:0",
86
+ /** @deprecated Size tiers are retired in 2.0. Use PROVIDER.BEDROCK.DEFAULT. */
83
87
  MODEL: {
84
88
  DEFAULT: "amazon.nova-lite-v1:0",
85
89
  LARGE: "amazon.nova-pro-v1:0",
@@ -100,22 +104,26 @@ const PROVIDER = {
100
104
  },
101
105
  ANTHROPIC: {
102
106
  // https://docs.anthropic.com/en/docs/about-claude/models/overview
107
+ DEFAULT: MODEL.SONNET,
103
108
  MAX_TOKENS: {
104
109
  // Non-streaming ceiling: responses above ~16K output tokens risk HTTP
105
110
  // timeouts; streaming requests resolve to the model maximum instead
106
111
  // (see util/maxOutputTokens.ts)
107
112
  DEFAULT: 16384,
108
113
  },
114
+ /** @deprecated Size tiers are retired in 2.0. Use PROVIDER.ANTHROPIC.DEFAULT, or pick a specific model from MODEL.*. */
109
115
  MODEL: {
110
- DEFAULT: FIRST_CLASS_PROVIDER.ANTHROPIC.DEFAULT,
111
- LARGE: FIRST_CLASS_PROVIDER.ANTHROPIC.LARGE,
112
- SMALL: FIRST_CLASS_PROVIDER.ANTHROPIC.SMALL,
113
- TINY: FIRST_CLASS_PROVIDER.ANTHROPIC.TINY,
116
+ DEFAULT: "claude-sonnet-4-6",
117
+ LARGE: "claude-opus-4-8",
118
+ SMALL: "claude-sonnet-4-6",
119
+ TINY: "claude-haiku-4-5",
114
120
  },
115
121
  MODEL_MATCH_WORDS: [
116
122
  "anthropic",
117
123
  "claude",
124
+ "fable",
118
125
  "haiku",
126
+ "mythos",
119
127
  "opus",
120
128
  "sonnet",
121
129
  ],
@@ -138,21 +146,25 @@ const PROVIDER = {
138
146
  GOOGLE: GOOGLE_PROVIDER,
139
147
  OPENAI: {
140
148
  // https://platform.openai.com/docs/models
149
+ DEFAULT: MODEL.SOL,
150
+ /** @deprecated Size tiers are retired in 2.0. Use PROVIDER.OPENAI.DEFAULT, or pick a specific model from MODEL.*. */
141
151
  MODEL: {
142
- DEFAULT: FIRST_CLASS_PROVIDER.OPENAI.DEFAULT,
143
- LARGE: FIRST_CLASS_PROVIDER.OPENAI.LARGE,
144
- SMALL: FIRST_CLASS_PROVIDER.OPENAI.SMALL,
145
- TINY: FIRST_CLASS_PROVIDER.OPENAI.TINY,
152
+ DEFAULT: "gpt-5.4",
153
+ LARGE: "gpt-5.5",
154
+ SMALL: "gpt-5.4-mini",
155
+ TINY: "gpt-5.4-nano",
146
156
  },
147
- MODEL_MATCH_WORDS: ["openai", "gpt", /^o\d/],
157
+ MODEL_MATCH_WORDS: ["gpt", "luna", "openai", "sol", "terra", /^o\d/],
148
158
  NAME: "openai",
149
159
  },
150
160
  OPENROUTER: {
161
+ DEFAULT: MODEL.OPENROUTER.SONNET,
162
+ /** @deprecated Size tiers are retired in 2.0. Use PROVIDER.OPENROUTER.DEFAULT, or pick a specific route from MODEL.OPENROUTER.*. */
151
163
  MODEL: {
152
- DEFAULT: `anthropic/${FIRST_CLASS_PROVIDER.ANTHROPIC.DEFAULT}`,
153
- LARGE: `anthropic/${FIRST_CLASS_PROVIDER.ANTHROPIC.LARGE}`,
154
- SMALL: `anthropic/${FIRST_CLASS_PROVIDER.ANTHROPIC.SMALL}`,
155
- TINY: `anthropic/${FIRST_CLASS_PROVIDER.ANTHROPIC.TINY}`,
164
+ DEFAULT: "anthropic/claude-sonnet-4-6",
165
+ LARGE: "anthropic/claude-opus-4-8",
166
+ SMALL: "anthropic/claude-sonnet-4-6",
167
+ TINY: "anthropic/claude-haiku-4-5",
156
168
  },
157
169
  MODEL_MATCH_WORDS: ["openrouter"],
158
170
  NAME: "openrouter",
@@ -167,11 +179,13 @@ const PROVIDER = {
167
179
  // https://docs.x.ai/docs/models
168
180
  API_KEY: "XAI_API_KEY",
169
181
  BASE_URL: "https://api.x.ai/v1",
182
+ DEFAULT: MODEL.GROK,
183
+ /** @deprecated Size tiers are retired in 2.0. Use PROVIDER.XAI.DEFAULT, or pick a specific model from MODEL.*. */
170
184
  MODEL: {
171
- DEFAULT: FIRST_CLASS_PROVIDER.XAI.DEFAULT,
172
- LARGE: FIRST_CLASS_PROVIDER.XAI.LARGE,
173
- SMALL: FIRST_CLASS_PROVIDER.XAI.SMALL,
174
- TINY: FIRST_CLASS_PROVIDER.XAI.TINY,
185
+ DEFAULT: "grok-latest",
186
+ LARGE: "grok-latest",
187
+ SMALL: "grok-4-1-fast-reasoning",
188
+ TINY: "grok-4-1-fast-non-reasoning",
175
189
  },
176
190
  MODEL_MATCH_WORDS: ["grok", "xai"],
177
191
  NAME: "xai",
@@ -179,6 +193,7 @@ const PROVIDER = {
179
193
  };
180
194
  // Last: Defaults
181
195
  const DEFAULT = {
196
+ /** @deprecated Size tiers are retired in 2.0. Use DEFAULT.PROVIDER.DEFAULT, or pick a specific model from MODEL.*. */
182
197
  MODEL: {
183
198
  BASE: PROVIDER.OPENAI.MODEL.DEFAULT,
184
199
  LARGE: PROVIDER.OPENAI.MODEL.LARGE,
@@ -187,6 +202,10 @@ const DEFAULT = {
187
202
  },
188
203
  PROVIDER: PROVIDER.OPENAI,
189
204
  };
205
+ /**
206
+ * @deprecated Size-tier catalogs are retired in 2.0. Pick specific models from
207
+ * MODEL.* (grouped by provider via MODEL_MATCH_WORDS / determineModelProvider).
208
+ */
190
209
  // Only include "first class" models, not OpenRouter or other proxy services
191
210
  const ALL = {
192
211
  BASE: [
@@ -239,6 +258,7 @@ var constants = /*#__PURE__*/Object.freeze({
239
258
  __proto__: null,
240
259
  ALL: ALL,
241
260
  DEFAULT: DEFAULT,
261
+ EFFORT: EFFORT,
242
262
  MODEL: MODEL,
243
263
  PROVIDER: PROVIDER
244
264
  });
@@ -246,7 +266,7 @@ var constants = /*#__PURE__*/Object.freeze({
246
266
  function determineModelProvider(input) {
247
267
  if (!input) {
248
268
  return {
249
- model: DEFAULT.PROVIDER.MODEL.DEFAULT,
269
+ model: DEFAULT.PROVIDER.DEFAULT,
250
270
  provider: DEFAULT.PROVIDER.NAME,
251
271
  };
252
272
  }
@@ -269,85 +289,42 @@ function determineModelProvider(input) {
269
289
  // Check if input is a provider name
270
290
  if (input === PROVIDER.BEDROCK.NAME) {
271
291
  return {
272
- model: PROVIDER.BEDROCK.MODEL.DEFAULT,
292
+ model: PROVIDER.BEDROCK.DEFAULT,
273
293
  provider: PROVIDER.BEDROCK.NAME,
274
294
  };
275
295
  }
276
296
  if (input === PROVIDER.ANTHROPIC.NAME) {
277
297
  return {
278
- model: PROVIDER.ANTHROPIC.MODEL.DEFAULT,
298
+ model: PROVIDER.ANTHROPIC.DEFAULT,
279
299
  provider: PROVIDER.ANTHROPIC.NAME,
280
300
  };
281
301
  }
282
302
  if (input === PROVIDER.GOOGLE.NAME || input === "gemini") {
283
303
  return {
284
- model: PROVIDER.GOOGLE.MODEL.DEFAULT,
304
+ model: PROVIDER.GOOGLE.DEFAULT,
285
305
  provider: PROVIDER.GOOGLE.NAME,
286
306
  };
287
307
  }
288
308
  if (input === PROVIDER.OPENAI.NAME) {
289
309
  return {
290
- model: PROVIDER.OPENAI.MODEL.DEFAULT,
310
+ model: PROVIDER.OPENAI.DEFAULT,
291
311
  provider: PROVIDER.OPENAI.NAME,
292
312
  };
293
313
  }
294
314
  if (input === PROVIDER.OPENROUTER.NAME) {
295
315
  return {
296
- model: PROVIDER.OPENROUTER.MODEL.DEFAULT,
316
+ model: PROVIDER.OPENROUTER.DEFAULT,
297
317
  provider: PROVIDER.OPENROUTER.NAME,
298
318
  };
299
319
  }
300
320
  if (input === PROVIDER.XAI.NAME) {
301
321
  return {
302
- model: PROVIDER.XAI.MODEL.DEFAULT,
322
+ model: PROVIDER.XAI.DEFAULT,
303
323
  provider: PROVIDER.XAI.NAME,
304
324
  };
305
325
  }
306
- // Check if input matches an Anthropic model exactly
307
- for (const [, modelValue] of Object.entries(PROVIDER.ANTHROPIC.MODEL)) {
308
- if (input === modelValue) {
309
- return {
310
- model: input,
311
- provider: PROVIDER.ANTHROPIC.NAME,
312
- };
313
- }
314
- }
315
- // Check if input matches a Gemini model exactly
316
- for (const [, modelValue] of Object.entries(PROVIDER.GOOGLE.MODEL)) {
317
- if (input === modelValue) {
318
- return {
319
- model: input,
320
- provider: PROVIDER.GOOGLE.NAME,
321
- };
322
- }
323
- }
324
- // Check if input matches an OpenAI model exactly
325
- for (const [, modelValue] of Object.entries(PROVIDER.OPENAI.MODEL)) {
326
- if (input === modelValue) {
327
- return {
328
- model: input,
329
- provider: PROVIDER.OPENAI.NAME,
330
- };
331
- }
332
- }
333
- // Check if input matches an OpenRouter model exactly
334
- for (const [, modelValue] of Object.entries(PROVIDER.OPENROUTER.MODEL)) {
335
- if (input === modelValue) {
336
- return {
337
- model: input,
338
- provider: PROVIDER.OPENROUTER.NAME,
339
- };
340
- }
341
- }
342
- // Check if input matches an xAI model exactly
343
- for (const [, modelValue] of Object.entries(PROVIDER.XAI.MODEL)) {
344
- if (input === modelValue) {
345
- return {
346
- model: input,
347
- provider: PROVIDER.XAI.NAME,
348
- };
349
- }
350
- }
326
+ // Exact model ids are classified by the "/" rule and MODEL_MATCH_WORDS below,
327
+ // so no per-provider id catalog is consulted here.
351
328
  // Assume OpenRouter for models containing "/" (e.g., "openai/gpt-4", "anthropic/claude-3-opus")
352
329
  // This check must come before match words so that "openai/gpt-4" is not matched by "openai" keyword
353
330
  if (input.includes("/")) {
@@ -427,6 +404,36 @@ function determineModelProvider(input) {
427
404
  };
428
405
  }
429
406
 
407
+ /**
408
+ * Normalizes a `model` option that may be a single model name or a
409
+ * preference-ordered array of model names.
410
+ *
411
+ * A `string[]` is interpreted as a fallback chain: index `0` is the primary
412
+ * model, indices `1..` become fallback entries with an auto-detected provider
413
+ * (reusing `determineModelProvider`, exactly as the constructor's first
414
+ * argument does).
415
+ *
416
+ * Returns the primary model plus the derived fallback chain. A bare string
417
+ * yields an empty chain and is unchanged.
418
+ */
419
+ function resolveModelChain(model) {
420
+ if (!Array.isArray(model)) {
421
+ return { fallback: [], model };
422
+ }
423
+ if (model.length === 0) {
424
+ throw new errors.ConfigurationError("model array must contain at least one model name");
425
+ }
426
+ const [primary, ...rest] = model;
427
+ const fallback = rest.map((name) => {
428
+ const determined = determineModelProvider(name);
429
+ if (!determined.provider) {
430
+ throw new errors.ConfigurationError(`Unable to determine provider from model: ${name}`);
431
+ }
432
+ return { model: determined.model, provider: determined.provider };
433
+ });
434
+ return { fallback, model: primary };
435
+ }
436
+
430
437
  //
431
438
  //
432
439
  // Abstract Base Adapter
@@ -464,6 +471,125 @@ class BaseProviderAdapter {
464
471
  }
465
472
  }
466
473
 
474
+ /**
475
+ * Per-provider translation of the provider-neutral {@link LlmEffort} scale.
476
+ *
477
+ * The neutral enum (lowest → low → medium → high → highest) is a relative
478
+ * five-point scale. Each table below maps it onto the target provider's native
479
+ * effort control, keeping `medium`/`high` semantically aligned across providers
480
+ * and using each provider's extra bottom (`minimal`) or top (`xhigh`/`max`)
481
+ * rung where one exists. Providers with fewer levels collapse the ends and mark
482
+ * the result `papered`. A single `effort` value is therefore safe to reuse
483
+ * across providers and fallback chains.
484
+ */
485
+ /** Consistent debug message for a papered-over effort level. */
486
+ function paperedEffortMessage({ model, provider, requested, value, }) {
487
+ return `[llm] effort '${requested}' has no distinct tier on ${provider} model '${model}'; using '${value}'`;
488
+ }
489
+ // OpenAI Responses API `reasoning.effort`.
490
+ // Full ladder: minimal | low | medium | high | xhigh (plus `none`). Availability
491
+ // is not uniform across the gpt-5 line:
492
+ // - `xhigh` was introduced at gpt-5.2 and has been continuous since, so it is
493
+ // safe for our gpt-5.4 default and everything newer.
494
+ // - `minimal` shipped on gpt-5/5.1, was dropped at gpt-5.2, and returned on
495
+ // the current line; because that history is non-monotonic we only trust it
496
+ // from gpt-5.4 (our default floor) onward.
497
+ // Outside those windows (older gpt-5, o-series) the extreme rung is clamped and
498
+ // the mapping reports `papered: true`.
499
+ const OPENAI_EFFORT = {
500
+ [EFFORT.LOWEST]: "minimal",
501
+ [EFFORT.LOW]: "low",
502
+ [EFFORT.MEDIUM]: "medium",
503
+ [EFFORT.HIGH]: "high",
504
+ [EFFORT.HIGHEST]: "xhigh",
505
+ };
506
+ function openAiGptVersion(model) {
507
+ const match = model.match(/^gpt-(\d+)(?:\.(\d+))?/);
508
+ if (!match)
509
+ return null;
510
+ return { major: Number(match[1]), minor: match[2] ? Number(match[2]) : 0 };
511
+ }
512
+ function atLeast(version, major, minor) {
513
+ if (!version)
514
+ return false;
515
+ return (version.major > major || (version.major === major && version.minor >= minor));
516
+ }
517
+ function toOpenAiEffort(effort, { model }) {
518
+ const native = OPENAI_EFFORT[effort];
519
+ const version = openAiGptVersion(model);
520
+ // `minimal` only from gpt-5.4 (non-monotonic history; absent on o-series)
521
+ if (native === "minimal" && !atLeast(version, 5, 4)) {
522
+ return { papered: true, value: "low" };
523
+ }
524
+ // `xhigh` from gpt-5.2 onward (absent on older gpt-5 and o-series)
525
+ if (native === "xhigh" && !atLeast(version, 5, 2)) {
526
+ return { papered: true, value: "high" };
527
+ }
528
+ return { papered: false, value: native };
529
+ }
530
+ // xAI Grok `reasoning_effort` — low | medium | high. No sub-low or top rung, so
531
+ // `lowest` collapses onto `low` and `highest` onto `high`.
532
+ const XAI_EFFORT = {
533
+ [EFFORT.LOWEST]: { papered: true, value: "low" },
534
+ [EFFORT.LOW]: { papered: false, value: "low" },
535
+ [EFFORT.MEDIUM]: { papered: false, value: "medium" },
536
+ [EFFORT.HIGH]: { papered: false, value: "high" },
537
+ [EFFORT.HIGHEST]: { papered: true, value: "high" },
538
+ };
539
+ function toXaiEffort(effort) {
540
+ return XAI_EFFORT[effort];
541
+ }
542
+ // Anthropic `output_config.effort` — low | medium | high | xhigh | max. No
543
+ // sub-low rung, so `lowest` collapses onto `low`; `highest` reaches `max`.
544
+ const ANTHROPIC_EFFORT = {
545
+ [EFFORT.LOWEST]: { papered: true, value: "low" },
546
+ [EFFORT.LOW]: { papered: false, value: "low" },
547
+ [EFFORT.MEDIUM]: { papered: false, value: "medium" },
548
+ [EFFORT.HIGH]: { papered: false, value: "high" },
549
+ [EFFORT.HIGHEST]: { papered: false, value: "max" },
550
+ };
551
+ function toAnthropicEffort(effort) {
552
+ return ANTHROPIC_EFFORT[effort];
553
+ }
554
+ // Gemini 3.x `thinkingConfig.thinkingLevel` — MINIMAL | LOW | MEDIUM | HIGH. No
555
+ // top rung above HIGH, so `highest` collapses onto HIGH.
556
+ const GEMINI_THINKING_LEVEL = {
557
+ [EFFORT.LOWEST]: { papered: false, value: "MINIMAL" },
558
+ [EFFORT.LOW]: { papered: false, value: "LOW" },
559
+ [EFFORT.MEDIUM]: { papered: false, value: "MEDIUM" },
560
+ [EFFORT.HIGH]: { papered: false, value: "HIGH" },
561
+ [EFFORT.HIGHEST]: { papered: true, value: "HIGH" },
562
+ };
563
+ function toGeminiThinkingLevel(effort) {
564
+ return GEMINI_THINKING_LEVEL[effort];
565
+ }
566
+ // Gemini 2.5 `thinkingConfig.thinkingBudget` — every tier is a distinct token
567
+ // budget, so nothing is papered over. Floor (512) clears every 2.5 minimum;
568
+ // ceiling (24,576) is valid across 2.5 Pro (max 32,768) and Flash (max 24,576).
569
+ const GEMINI_THINKING_BUDGET = {
570
+ [EFFORT.LOWEST]: 512,
571
+ [EFFORT.LOW]: 4096,
572
+ [EFFORT.MEDIUM]: 8192,
573
+ [EFFORT.HIGH]: 16384,
574
+ [EFFORT.HIGHEST]: 24576,
575
+ };
576
+ function toGeminiThinkingBudget(effort) {
577
+ return { papered: false, value: GEMINI_THINKING_BUDGET[effort] };
578
+ }
579
+ // OpenRouter `reasoning.effort` — accepts the full minimal..xhigh ladder and
580
+ // maps to the routed provider's nearest supported level itself, so nothing is
581
+ // papered here.
582
+ const OPENROUTER_EFFORT = {
583
+ [EFFORT.LOWEST]: "minimal",
584
+ [EFFORT.LOW]: "low",
585
+ [EFFORT.MEDIUM]: "medium",
586
+ [EFFORT.HIGH]: "high",
587
+ [EFFORT.HIGHEST]: "xhigh",
588
+ };
589
+ function toOpenRouterEffort(effort) {
590
+ return { papered: false, value: OPENROUTER_EFFORT[effort] };
591
+ }
592
+
467
593
  // Enums
468
594
  exports.LlmMessageRole = void 0;
469
595
  (function (LlmMessageRole) {
@@ -1393,17 +1519,23 @@ function tryParseNumber(input, options) {
1393
1519
  /**
1394
1520
  * Categories of errors for retry logic
1395
1521
  */
1396
- var ErrorCategory;
1522
+ exports.ErrorCategory = void 0;
1397
1523
  (function (ErrorCategory) {
1398
1524
  /** Error is transient and can be retried */
1399
1525
  ErrorCategory["Retryable"] = "retryable";
1400
- /** Error is due to rate limiting */
1526
+ /** Error is due to short-term rate limiting (retry after a delay) */
1401
1527
  ErrorCategory["RateLimit"] = "rate_limit";
1528
+ /**
1529
+ * Provider quota is exhausted or the account cannot be billed
1530
+ * (insufficient funds, daily quota, plan limit). Terminal and actionable —
1531
+ * retrying within the request budget will not help.
1532
+ */
1533
+ ErrorCategory["Quota"] = "quota";
1402
1534
  /** Error cannot be recovered from */
1403
1535
  ErrorCategory["Unrecoverable"] = "unrecoverable";
1404
1536
  /** Error type is unknown */
1405
1537
  ErrorCategory["Unknown"] = "unknown";
1406
- })(ErrorCategory || (ErrorCategory = {}));
1538
+ })(exports.ErrorCategory || (exports.ErrorCategory = {}));
1407
1539
 
1408
1540
  /**
1409
1541
  * Transient network error detection utility.
@@ -1489,11 +1621,120 @@ function isTransientNetworkError(error) {
1489
1621
  return false;
1490
1622
  }
1491
1623
 
1624
+ //
1625
+ //
1626
+ // Constants
1627
+ //
1628
+ /**
1629
+ * Transient structured-output compile failures. The provider caches the
1630
+ * compiled grammar after a successful compile, so an immediate retry of the
1631
+ * identical request typically succeeds (issue #422).
1632
+ */
1633
+ const RETRYABLE_MESSAGE_PATTERNS = [
1634
+ "grammar compilation timed out",
1635
+ "grammar compilation timeout",
1636
+ ];
1637
+ /** Billing / insufficient-funds signals — the account cannot be charged. */
1638
+ const BILLING_MESSAGE_PATTERNS = [
1639
+ "insufficient_quota",
1640
+ "insufficient funds",
1641
+ "insufficient credit",
1642
+ "credit balance",
1643
+ "billing",
1644
+ "payment required",
1645
+ "plan and billing",
1646
+ ];
1647
+ /** Exhausted usage quota (per-day / per-model limits). */
1648
+ const QUOTA_MESSAGE_PATTERNS = [
1649
+ "quota exceeded",
1650
+ "exceeded your current quota",
1651
+ "resource_exhausted",
1652
+ "quota_exceeded",
1653
+ ];
1654
+ //
1655
+ //
1656
+ // Helpers
1657
+ //
1658
+ function extractMessage(error) {
1659
+ if (error instanceof Error)
1660
+ return error.message.toLowerCase();
1661
+ if (typeof error === "string")
1662
+ return error.toLowerCase();
1663
+ if (error && typeof error === "object") {
1664
+ const message = error.message;
1665
+ if (typeof message === "string")
1666
+ return message.toLowerCase();
1667
+ }
1668
+ return "";
1669
+ }
1670
+ //
1671
+ //
1672
+ // Main
1673
+ //
1674
+ /**
1675
+ * Shared, provider-agnostic first pass over an error. Returns a
1676
+ * {@link ClassifiedError} when the message unambiguously identifies a
1677
+ * cross-provider condition (retryable structured-output timeout, exhausted
1678
+ * quota, or a billing failure), or `undefined` to defer to the adapter's own
1679
+ * status/name classification.
1680
+ *
1681
+ * Adapters call this before their existing logic so that, for example, a
1682
+ * `429` carrying a daily-quota message is classified as {@link
1683
+ * ErrorCategory.Quota} rather than {@link ErrorCategory.RateLimit}.
1684
+ */
1685
+ function classifyProviderError(error) {
1686
+ const message = extractMessage(error);
1687
+ if (!message)
1688
+ return undefined;
1689
+ for (const pattern of RETRYABLE_MESSAGE_PATTERNS) {
1690
+ if (message.includes(pattern)) {
1691
+ return { category: exports.ErrorCategory.Retryable, error, shouldRetry: true };
1692
+ }
1693
+ }
1694
+ for (const pattern of BILLING_MESSAGE_PATTERNS) {
1695
+ if (message.includes(pattern)) {
1696
+ return {
1697
+ category: exports.ErrorCategory.Quota,
1698
+ error,
1699
+ reason: "billing",
1700
+ shouldRetry: false,
1701
+ };
1702
+ }
1703
+ }
1704
+ for (const pattern of QUOTA_MESSAGE_PATTERNS) {
1705
+ if (message.includes(pattern)) {
1706
+ return {
1707
+ category: exports.ErrorCategory.Quota,
1708
+ error,
1709
+ reason: "quota",
1710
+ shouldRetry: false,
1711
+ };
1712
+ }
1713
+ }
1714
+ return undefined;
1715
+ }
1716
+
1492
1717
  //
1493
1718
  //
1494
1719
  // Constants
1495
1720
  //
1496
1721
  const STRUCTURED_OUTPUT_TOOL_NAME$3 = "structured_output";
1722
+ /**
1723
+ * Whether `output_config.effort` may be sent for this model. Anthropic added
1724
+ * the effort control on the Claude 4.5 line and up (the 5 family); older models
1725
+ * reject it. Parsing major/minor avoids matching a minor "5" in legacy ids
1726
+ * like `claude-3-5-sonnet`.
1727
+ */
1728
+ function supportsAnthropicEffort(model) {
1729
+ const match = model.match(/claude-[a-z]+-(\d+)(?:-(\d+))?/);
1730
+ if (!match)
1731
+ return false;
1732
+ const major = Number(match[1]);
1733
+ const minor = match[2] !== undefined ? Number(match[2]) : 0;
1734
+ if (major >= 5)
1735
+ return true;
1736
+ return major === 4 && minor >= 5;
1737
+ }
1497
1738
  const STRUCTURED_OUTPUT_NON_PARSE_STOP_REASONS = new Set([
1498
1739
  "refusal",
1499
1740
  "max_tokens",
@@ -1759,7 +2000,7 @@ class AnthropicAdapter extends BaseProviderAdapter {
1759
2000
  constructor() {
1760
2001
  super(...arguments);
1761
2002
  this.name = PROVIDER.ANTHROPIC.NAME;
1762
- this.defaultModel = PROVIDER.ANTHROPIC.MODEL.DEFAULT;
2003
+ this.defaultModel = PROVIDER.ANTHROPIC.DEFAULT;
1763
2004
  // Session-level cache of models observed to reject `temperature` at runtime.
1764
2005
  // Populated by executeRequest on 400 errors so repeat calls skip the param.
1765
2006
  this.runtimeNoTemperatureModels = new Set();
@@ -1899,6 +2140,24 @@ class AnthropicAdapter extends BaseProviderAdapter {
1899
2140
  if (request.providerOptions) {
1900
2141
  Object.assign(anthropicRequest, request.providerOptions);
1901
2142
  }
2143
+ // Normalized reasoning effort -> output_config.effort (merged so a format
2144
+ // config above survives). First-class effort wins over providerOptions.
2145
+ if (request.effort &&
2146
+ supportsAnthropicEffort(anthropicRequest.model)) {
2147
+ const mapping = toAnthropicEffort(request.effort);
2148
+ if (mapping.papered) {
2149
+ log$1.log.debug(paperedEffortMessage({
2150
+ model: anthropicRequest.model,
2151
+ provider: this.name,
2152
+ requested: request.effort,
2153
+ value: mapping.value,
2154
+ }));
2155
+ }
2156
+ anthropicRequest.output_config = {
2157
+ ...anthropicRequest.output_config,
2158
+ effort: mapping.value,
2159
+ };
2160
+ }
1902
2161
  // First-class temperature takes precedence over providerOptions
1903
2162
  if (request.temperature !== undefined) {
1904
2163
  anthropicRequest.temperature = request.temperature;
@@ -1993,9 +2252,13 @@ class AnthropicAdapter extends BaseProviderAdapter {
1993
2252
  */
1994
2253
  toFallbackStructuredOutputRequest(request) {
1995
2254
  const { output_config, ...rest } = request;
1996
- if (!output_config)
2255
+ if (!output_config?.format)
1997
2256
  return request;
1998
2257
  const fallbackRequest = { ...rest };
2258
+ // Preserve an effort setting; only the structured-output format falls back.
2259
+ if (output_config.effort) {
2260
+ fallbackRequest.output_config = { effort: output_config.effort };
2261
+ }
1999
2262
  const fakeTool = {
2000
2263
  name: STRUCTURED_OUTPUT_TOOL_NAME$3,
2001
2264
  description: "Output a structured JSON object, " +
@@ -2224,12 +2487,17 @@ class AnthropicAdapter extends BaseProviderAdapter {
2224
2487
  // Error Classification
2225
2488
  //
2226
2489
  classifyError(error) {
2490
+ // Shared first pass: retryable structured-output timeouts (#422),
2491
+ // quota exhaustion, and billing failures classify the same across providers.
2492
+ const shared = classifyProviderError(error);
2493
+ if (shared)
2494
+ return shared;
2227
2495
  const errorName = error?.constructor?.name;
2228
2496
  // Check for rate limit error
2229
2497
  if (errorName === "RateLimitError") {
2230
2498
  return {
2231
2499
  error,
2232
- category: ErrorCategory.RateLimit,
2500
+ category: exports.ErrorCategory.RateLimit,
2233
2501
  shouldRetry: false,
2234
2502
  suggestedDelayMs: 60000,
2235
2503
  };
@@ -2238,7 +2506,7 @@ class AnthropicAdapter extends BaseProviderAdapter {
2238
2506
  if (RETRYABLE_ERROR_NAMES.includes(errorName)) {
2239
2507
  return {
2240
2508
  error,
2241
- category: ErrorCategory.Retryable,
2509
+ category: exports.ErrorCategory.Retryable,
2242
2510
  shouldRetry: true,
2243
2511
  };
2244
2512
  }
@@ -2246,7 +2514,7 @@ class AnthropicAdapter extends BaseProviderAdapter {
2246
2514
  if (NOT_RETRYABLE_ERROR_NAMES.includes(errorName)) {
2247
2515
  return {
2248
2516
  error,
2249
- category: ErrorCategory.Unrecoverable,
2517
+ category: exports.ErrorCategory.Unrecoverable,
2250
2518
  shouldRetry: false,
2251
2519
  };
2252
2520
  }
@@ -2254,14 +2522,14 @@ class AnthropicAdapter extends BaseProviderAdapter {
2254
2522
  if (isTransientNetworkError(error)) {
2255
2523
  return {
2256
2524
  error,
2257
- category: ErrorCategory.Retryable,
2525
+ category: exports.ErrorCategory.Retryable,
2258
2526
  shouldRetry: true,
2259
2527
  };
2260
2528
  }
2261
2529
  // Unknown error - treat as potentially retryable
2262
2530
  return {
2263
2531
  error,
2264
- category: ErrorCategory.Unknown,
2532
+ category: exports.ErrorCategory.Unknown,
2265
2533
  shouldRetry: true,
2266
2534
  };
2267
2535
  }
@@ -2439,7 +2707,7 @@ class BedrockAdapter extends BaseProviderAdapter {
2439
2707
  constructor() {
2440
2708
  super(...arguments);
2441
2709
  this.name = PROVIDER.BEDROCK.NAME;
2442
- this.defaultModel = PROVIDER.BEDROCK.MODEL.DEFAULT;
2710
+ this.defaultModel = PROVIDER.BEDROCK.DEFAULT;
2443
2711
  this._modelsFallbackToStructuredOutputTool = new Set();
2444
2712
  this._modelsWithoutTemperature = new Set();
2445
2713
  }
@@ -2832,6 +3100,11 @@ class BedrockAdapter extends BaseProviderAdapter {
2832
3100
  // Error Classification
2833
3101
  //
2834
3102
  classifyError(error) {
3103
+ // Shared first pass: retryable structured-output timeouts (#422),
3104
+ // quota exhaustion, and billing failures classify the same across providers.
3105
+ const shared = classifyProviderError(error);
3106
+ if (shared)
3107
+ return shared;
2835
3108
  const errorName = error?.constructor?.name;
2836
3109
  const errorMessage = error?.message ?? "";
2837
3110
  if (errorName === "ThrottlingException" ||
@@ -2839,7 +3112,7 @@ class BedrockAdapter extends BaseProviderAdapter {
2839
3112
  errorMessage.includes("Too Many Requests")) {
2840
3113
  return {
2841
3114
  error,
2842
- category: ErrorCategory.RateLimit,
3115
+ category: exports.ErrorCategory.RateLimit,
2843
3116
  shouldRetry: false,
2844
3117
  suggestedDelayMs: 60000,
2845
3118
  };
@@ -2850,7 +3123,7 @@ class BedrockAdapter extends BaseProviderAdapter {
2850
3123
  errorMessage.includes("InternalServerException")) {
2851
3124
  return {
2852
3125
  error,
2853
- category: ErrorCategory.Retryable,
3126
+ category: exports.ErrorCategory.Retryable,
2854
3127
  shouldRetry: true,
2855
3128
  };
2856
3129
  }
@@ -2861,20 +3134,20 @@ class BedrockAdapter extends BaseProviderAdapter {
2861
3134
  errorMessage.includes("ValidationException")) {
2862
3135
  return {
2863
3136
  error,
2864
- category: ErrorCategory.Unrecoverable,
3137
+ category: exports.ErrorCategory.Unrecoverable,
2865
3138
  shouldRetry: false,
2866
3139
  };
2867
3140
  }
2868
3141
  if (isTransientNetworkError(error)) {
2869
3142
  return {
2870
3143
  error,
2871
- category: ErrorCategory.Retryable,
3144
+ category: exports.ErrorCategory.Retryable,
2872
3145
  shouldRetry: true,
2873
3146
  };
2874
3147
  }
2875
3148
  return {
2876
3149
  error,
2877
- category: ErrorCategory.Unknown,
3150
+ category: exports.ErrorCategory.Unknown,
2878
3151
  shouldRetry: true,
2879
3152
  };
2880
3153
  }
@@ -2945,6 +3218,11 @@ const STRUCTURED_OUTPUT_TOOL_NAME$1 = "structured_output";
2945
3218
  // (including 2.5 thinking) do not support the combo and fall back to the
2946
3219
  // legacy `structured_output` fake-tool emulation.
2947
3220
  const GEMINI_3_PATTERN = /^gemini-3/;
3221
+ // Reasoning-effort control differs by generation: Gemini 3.x takes the
3222
+ // `thinkingLevel` enum, Gemini 2.5 takes an integer `thinkingBudget`. Sending
3223
+ // the wrong one errors, and models outside these families have no thinking
3224
+ // control, so effort is only applied when one of these matches.
3225
+ const GEMINI_25_PATTERN = /^gemini-2\.5/;
2948
3226
  /**
2949
3227
  * Detect 4xx errors that indicate the model itself does not support the
2950
3228
  * `responseJsonSchema` + tools combo. Triggers the runtime fallback to the
@@ -2991,7 +3269,7 @@ class GoogleAdapter extends BaseProviderAdapter {
2991
3269
  constructor() {
2992
3270
  super(...arguments);
2993
3271
  this.name = PROVIDER.GOOGLE.NAME;
2994
- this.defaultModel = PROVIDER.GOOGLE.MODEL.DEFAULT;
3272
+ this.defaultModel = PROVIDER.GOOGLE.DEFAULT;
2995
3273
  // Session-level cache of Gemini 3 models observed to reject the native
2996
3274
  // `responseJsonSchema` + tools combo. When a model is in this set,
2997
3275
  // buildRequest engages the legacy fake-tool path instead.
@@ -3119,6 +3397,42 @@ class GoogleAdapter extends BaseProviderAdapter {
3119
3397
  ...request.providerOptions,
3120
3398
  };
3121
3399
  }
3400
+ // Normalized reasoning effort -> thinkingConfig. Gemini 3.x uses the
3401
+ // thinkingLevel enum; 2.5 uses an integer thinkingBudget. Merged so a
3402
+ // providerOptions.thinkingConfig survives; first-class effort wins.
3403
+ if (request.effort) {
3404
+ const model = geminiRequest.model;
3405
+ let thinkingConfig;
3406
+ let papered = false;
3407
+ let value;
3408
+ if (GEMINI_3_PATTERN.test(model)) {
3409
+ const mapping = toGeminiThinkingLevel(request.effort);
3410
+ ({ papered, value } = mapping);
3411
+ thinkingConfig = { thinkingLevel: mapping.value };
3412
+ }
3413
+ else if (GEMINI_25_PATTERN.test(model)) {
3414
+ const mapping = toGeminiThinkingBudget(request.effort);
3415
+ ({ papered, value } = mapping);
3416
+ thinkingConfig = { thinkingBudget: mapping.value };
3417
+ }
3418
+ if (thinkingConfig) {
3419
+ if (papered) {
3420
+ log$1.log.debug(paperedEffortMessage({
3421
+ model,
3422
+ provider: this.name,
3423
+ requested: request.effort,
3424
+ value: value,
3425
+ }));
3426
+ }
3427
+ geminiRequest.config = {
3428
+ ...geminiRequest.config,
3429
+ thinkingConfig: {
3430
+ ...geminiRequest.config?.thinkingConfig,
3431
+ ...thinkingConfig,
3432
+ },
3433
+ };
3434
+ }
3435
+ }
3122
3436
  // First-class temperature takes precedence over providerOptions
3123
3437
  if (request.temperature !== undefined) {
3124
3438
  geminiRequest.config = {
@@ -3460,6 +3774,11 @@ class GoogleAdapter extends BaseProviderAdapter {
3460
3774
  // Error Classification
3461
3775
  //
3462
3776
  classifyError(error) {
3777
+ // Shared first pass: retryable structured-output timeouts (#422),
3778
+ // quota exhaustion, and billing failures classify the same across providers.
3779
+ const shared = classifyProviderError(error);
3780
+ if (shared)
3781
+ return shared;
3463
3782
  const geminiError = error;
3464
3783
  // Extract status code from error
3465
3784
  const statusCode = geminiError.status || geminiError.code;
@@ -3467,7 +3786,7 @@ class GoogleAdapter extends BaseProviderAdapter {
3467
3786
  if (statusCode === 429) {
3468
3787
  return {
3469
3788
  error,
3470
- category: ErrorCategory.RateLimit,
3789
+ category: exports.ErrorCategory.RateLimit,
3471
3790
  shouldRetry: false,
3472
3791
  suggestedDelayMs: 60000,
3473
3792
  };
@@ -3477,7 +3796,7 @@ class GoogleAdapter extends BaseProviderAdapter {
3477
3796
  RETRYABLE_STATUS_CODES$1.includes(statusCode)) {
3478
3797
  return {
3479
3798
  error,
3480
- category: ErrorCategory.Retryable,
3799
+ category: exports.ErrorCategory.Retryable,
3481
3800
  shouldRetry: true,
3482
3801
  };
3483
3802
  }
@@ -3486,7 +3805,7 @@ class GoogleAdapter extends BaseProviderAdapter {
3486
3805
  NOT_RETRYABLE_STATUS_CODES.includes(statusCode)) {
3487
3806
  return {
3488
3807
  error,
3489
- category: ErrorCategory.Unrecoverable,
3808
+ category: exports.ErrorCategory.Unrecoverable,
3490
3809
  shouldRetry: false,
3491
3810
  };
3492
3811
  }
@@ -3496,7 +3815,7 @@ class GoogleAdapter extends BaseProviderAdapter {
3496
3815
  errorMessage.includes("quota exceeded")) {
3497
3816
  return {
3498
3817
  error,
3499
- category: ErrorCategory.RateLimit,
3818
+ category: exports.ErrorCategory.RateLimit,
3500
3819
  shouldRetry: false,
3501
3820
  suggestedDelayMs: 60000,
3502
3821
  };
@@ -3506,7 +3825,7 @@ class GoogleAdapter extends BaseProviderAdapter {
3506
3825
  errorMessage.includes("ECONNREFUSED")) {
3507
3826
  return {
3508
3827
  error,
3509
- category: ErrorCategory.Retryable,
3828
+ category: exports.ErrorCategory.Retryable,
3510
3829
  shouldRetry: true,
3511
3830
  };
3512
3831
  }
@@ -3514,14 +3833,14 @@ class GoogleAdapter extends BaseProviderAdapter {
3514
3833
  if (isTransientNetworkError(error)) {
3515
3834
  return {
3516
3835
  error,
3517
- category: ErrorCategory.Retryable,
3836
+ category: exports.ErrorCategory.Retryable,
3518
3837
  shouldRetry: true,
3519
3838
  };
3520
3839
  }
3521
3840
  // Unknown error - treat as potentially retryable
3522
3841
  return {
3523
3842
  error,
3524
- category: ErrorCategory.Unknown,
3843
+ category: exports.ErrorCategory.Unknown,
3525
3844
  shouldRetry: true,
3526
3845
  };
3527
3846
  }
@@ -4038,7 +4357,7 @@ class OpenAiAdapter extends BaseProviderAdapter {
4038
4357
  constructor() {
4039
4358
  super(...arguments);
4040
4359
  this.name = PROVIDER.OPENAI.NAME;
4041
- this.defaultModel = PROVIDER.OPENAI.MODEL.DEFAULT;
4360
+ this.defaultModel = PROVIDER.OPENAI.DEFAULT;
4042
4361
  // Session-level cache of models observed to reject `temperature` at runtime.
4043
4362
  // Populated by executeRequest on 400 errors so repeat calls skip the param.
4044
4363
  this.runtimeNoTemperatureModels = new Set();
@@ -4054,6 +4373,14 @@ class OpenAiAdapter extends BaseProviderAdapter {
4054
4373
  return false;
4055
4374
  return !MODELS_WITHOUT_TEMPERATURE$1.some((pattern) => pattern.test(model));
4056
4375
  }
4376
+ /** Whether `reasoning.effort` may be sent for this model. Overridden by xAI. */
4377
+ supportsReasoningEffort(model) {
4378
+ return isReasoningModel(model);
4379
+ }
4380
+ /** Translate a normalized effort to this provider's `reasoning.effort` value. */
4381
+ mapReasoningEffort(effort, model) {
4382
+ return toOpenAiEffort(effort, { model });
4383
+ }
4057
4384
  //
4058
4385
  // Request Building
4059
4386
  //
@@ -4092,6 +4419,24 @@ class OpenAiAdapter extends BaseProviderAdapter {
4092
4419
  if (request.providerOptions) {
4093
4420
  Object.assign(openaiRequest, request.providerOptions);
4094
4421
  }
4422
+ // Normalized reasoning effort -> reasoning.effort (merged so the
4423
+ // summary:auto above survives). First-class effort wins over providerOptions.
4424
+ if (request.effort && this.supportsReasoningEffort(model)) {
4425
+ const mapping = this.mapReasoningEffort(request.effort, model);
4426
+ if (mapping.papered) {
4427
+ log$1.log.debug(paperedEffortMessage({
4428
+ model,
4429
+ provider: this.name,
4430
+ requested: request.effort,
4431
+ value: mapping.value,
4432
+ }));
4433
+ }
4434
+ const existingReasoning = openaiRequest.reasoning ?? {};
4435
+ openaiRequest.reasoning = {
4436
+ ...existingReasoning,
4437
+ effort: mapping.value,
4438
+ };
4439
+ }
4095
4440
  // First-class temperature takes precedence over providerOptions
4096
4441
  if (request.temperature !== undefined) {
4097
4442
  openaiRequest.temperature = request.temperature;
@@ -4382,11 +4727,16 @@ class OpenAiAdapter extends BaseProviderAdapter {
4382
4727
  // Error Classification
4383
4728
  //
4384
4729
  classifyError(error) {
4730
+ // Shared first pass: retryable structured-output timeouts (#422),
4731
+ // quota exhaustion, and billing failures classify the same across providers.
4732
+ const shared = classifyProviderError(error);
4733
+ if (shared)
4734
+ return shared;
4385
4735
  // Check for rate limit error
4386
4736
  if (error instanceof RateLimitError$1) {
4387
4737
  return {
4388
4738
  error,
4389
- category: ErrorCategory.RateLimit,
4739
+ category: exports.ErrorCategory.RateLimit,
4390
4740
  shouldRetry: false, // Rate limit requires waiting, not immediate retry
4391
4741
  suggestedDelayMs: 60000, // 1 minute default
4392
4742
  };
@@ -4396,7 +4746,7 @@ class OpenAiAdapter extends BaseProviderAdapter {
4396
4746
  if (error instanceof ErrorType) {
4397
4747
  return {
4398
4748
  error,
4399
- category: ErrorCategory.Retryable,
4749
+ category: exports.ErrorCategory.Retryable,
4400
4750
  shouldRetry: true,
4401
4751
  };
4402
4752
  }
@@ -4406,7 +4756,7 @@ class OpenAiAdapter extends BaseProviderAdapter {
4406
4756
  if (error instanceof ErrorType) {
4407
4757
  return {
4408
4758
  error,
4409
- category: ErrorCategory.Unrecoverable,
4759
+ category: exports.ErrorCategory.Unrecoverable,
4410
4760
  shouldRetry: false,
4411
4761
  };
4412
4762
  }
@@ -4415,14 +4765,14 @@ class OpenAiAdapter extends BaseProviderAdapter {
4415
4765
  if (isTransientNetworkError(error)) {
4416
4766
  return {
4417
4767
  error,
4418
- category: ErrorCategory.Retryable,
4768
+ category: exports.ErrorCategory.Retryable,
4419
4769
  shouldRetry: true,
4420
4770
  };
4421
4771
  }
4422
4772
  // Unknown error - treat as potentially retryable
4423
4773
  return {
4424
4774
  error,
4425
- category: ErrorCategory.Unknown,
4775
+ category: exports.ErrorCategory.Unknown,
4426
4776
  shouldRetry: true,
4427
4777
  };
4428
4778
  }
@@ -4686,7 +5036,7 @@ class OpenRouterAdapter extends BaseProviderAdapter {
4686
5036
  constructor() {
4687
5037
  super(...arguments);
4688
5038
  this.name = PROVIDER.OPENROUTER.NAME;
4689
- this.defaultModel = PROVIDER.OPENROUTER.MODEL.DEFAULT;
5039
+ this.defaultModel = PROVIDER.OPENROUTER.DEFAULT;
4690
5040
  // Session-level cache of models observed to reject native
4691
5041
  // `response_format: json_schema`. When a model is in this set, buildRequest
4692
5042
  // engages the legacy fake-tool path instead of native structured output.
@@ -4779,6 +5129,15 @@ class OpenRouterAdapter extends BaseProviderAdapter {
4779
5129
  if (request.providerOptions) {
4780
5130
  Object.assign(openRouterRequest, request.providerOptions);
4781
5131
  }
5132
+ // Normalized reasoning effort -> reasoning.effort. OpenRouter accepts the
5133
+ // full ladder and maps to the routed provider's nearest supported level.
5134
+ // First-class effort wins over providerOptions.
5135
+ if (request.effort) {
5136
+ openRouterRequest.reasoning = {
5137
+ ...openRouterRequest.reasoning,
5138
+ effort: toOpenRouterEffort(request.effort).value,
5139
+ };
5140
+ }
4782
5141
  // First-class temperature takes precedence over providerOptions
4783
5142
  if (request.temperature !== undefined) {
4784
5143
  openRouterRequest.temperature =
@@ -5141,6 +5500,11 @@ class OpenRouterAdapter extends BaseProviderAdapter {
5141
5500
  // Error Classification
5142
5501
  //
5143
5502
  classifyError(error) {
5503
+ // Shared first pass: retryable structured-output timeouts (#422),
5504
+ // quota exhaustion, and billing failures classify the same across providers.
5505
+ const shared = classifyProviderError(error);
5506
+ if (shared)
5507
+ return shared;
5144
5508
  // Check if error has a status code (HTTP error)
5145
5509
  const errorWithStatus = error;
5146
5510
  const statusCode = errorWithStatus.status || errorWithStatus.statusCode;
@@ -5149,7 +5513,7 @@ class OpenRouterAdapter extends BaseProviderAdapter {
5149
5513
  if (statusCode === RATE_LIMIT_STATUS_CODE) {
5150
5514
  return {
5151
5515
  error,
5152
- category: ErrorCategory.RateLimit,
5516
+ category: exports.ErrorCategory.RateLimit,
5153
5517
  shouldRetry: false,
5154
5518
  suggestedDelayMs: 60000,
5155
5519
  };
@@ -5158,7 +5522,7 @@ class OpenRouterAdapter extends BaseProviderAdapter {
5158
5522
  if (RETRYABLE_STATUS_CODES.includes(statusCode)) {
5159
5523
  return {
5160
5524
  error,
5161
- category: ErrorCategory.Retryable,
5525
+ category: exports.ErrorCategory.Retryable,
5162
5526
  shouldRetry: true,
5163
5527
  };
5164
5528
  }
@@ -5166,7 +5530,7 @@ class OpenRouterAdapter extends BaseProviderAdapter {
5166
5530
  if (statusCode >= 400 && statusCode < 500) {
5167
5531
  return {
5168
5532
  error,
5169
- category: ErrorCategory.Unrecoverable,
5533
+ category: exports.ErrorCategory.Unrecoverable,
5170
5534
  shouldRetry: false,
5171
5535
  };
5172
5536
  }
@@ -5177,7 +5541,7 @@ class OpenRouterAdapter extends BaseProviderAdapter {
5177
5541
  errorMessage.includes("too many requests")) {
5178
5542
  return {
5179
5543
  error,
5180
- category: ErrorCategory.RateLimit,
5544
+ category: exports.ErrorCategory.RateLimit,
5181
5545
  shouldRetry: false,
5182
5546
  suggestedDelayMs: 60000,
5183
5547
  };
@@ -5186,14 +5550,14 @@ class OpenRouterAdapter extends BaseProviderAdapter {
5186
5550
  if (isTransientNetworkError(error)) {
5187
5551
  return {
5188
5552
  error,
5189
- category: ErrorCategory.Retryable,
5553
+ category: exports.ErrorCategory.Retryable,
5190
5554
  shouldRetry: true,
5191
5555
  };
5192
5556
  }
5193
5557
  // Unknown error - treat as potentially retryable
5194
5558
  return {
5195
5559
  error,
5196
- category: ErrorCategory.Unknown,
5560
+ category: exports.ErrorCategory.Unknown,
5197
5561
  shouldRetry: true,
5198
5562
  };
5199
5563
  }
@@ -5397,14 +5761,27 @@ class XaiAdapter extends OpenAiAdapter {
5397
5761
  super(...arguments);
5398
5762
  // @ts-expect-error Narrowing override: xAI name differs from parent's literal "openai"
5399
5763
  this.name = PROVIDER.XAI.NAME;
5400
- // @ts-expect-error Narrowing override: xAI default model differs from parent's literal
5401
- this.defaultModel = PROVIDER.XAI.MODEL.DEFAULT;
5764
+ this.defaultModel = PROVIDER.XAI.DEFAULT;
5765
+ }
5766
+ /**
5767
+ * Grok gates reasoning effort by model, not by the OpenAI `gpt-*`/`o*`
5768
+ * patterns. Only explicit `*-reasoning` models accept `reasoning_effort`;
5769
+ * bare grok-4 reasons implicitly and rejects it, so we stay conservative and
5770
+ * only opt in models whose name advertises reasoning.
5771
+ */
5772
+ supportsReasoningEffort(model) {
5773
+ if (/non-reasoning/.test(model))
5774
+ return false;
5775
+ return /reasoning/.test(model);
5776
+ }
5777
+ mapReasoningEffort(effort) {
5778
+ return toXaiEffort(effort);
5402
5779
  }
5403
5780
  classifyError(error) {
5404
5781
  if (isTransientIngestError(error)) {
5405
5782
  return {
5406
5783
  error,
5407
- category: ErrorCategory.Retryable,
5784
+ category: exports.ErrorCategory.Retryable,
5408
5785
  shouldRetry: true,
5409
5786
  };
5410
5787
  }
@@ -6492,6 +6869,122 @@ function createStaleRejectionGuard() {
6492
6869
  };
6493
6870
  }
6494
6871
 
6872
+ //
6873
+ //
6874
+ // Base
6875
+ //
6876
+ /**
6877
+ * Normalized, provider-agnostic LLM error. Thrown by the operate/stream retry
6878
+ * layer when a request cannot be completed, so consumers can `catch` a stable
6879
+ * type regardless of which provider failed. Extends {@link JaypieError} so
6880
+ * `isJaypieError()` and `.status` continue to work; the original provider error
6881
+ * is preserved on `.cause`.
6882
+ */
6883
+ class LlmError extends errors.JaypieError {
6884
+ constructor(message, category, { status = 502, title = "Bad Gateway", provider, model, retryAfterMs, cause, } = {}) {
6885
+ super(message, { status, title }, { _type: "LlmError" });
6886
+ this.name = "LlmError";
6887
+ this.category = category;
6888
+ this.provider = provider;
6889
+ this.model = model;
6890
+ this.retryAfterMs = retryAfterMs;
6891
+ this.cause = cause;
6892
+ }
6893
+ }
6894
+ //
6895
+ //
6896
+ // Subclasses
6897
+ //
6898
+ /**
6899
+ * Short-term rate limiting (per-minute 429). Terminal within the request
6900
+ * budget; `retryAfterMs` carries the provider's suggested wait when available.
6901
+ */
6902
+ class LlmRateLimitError extends LlmError {
6903
+ constructor(message, options = {}) {
6904
+ super(message, exports.ErrorCategory.RateLimit, {
6905
+ status: 429,
6906
+ title: "Too Many Requests",
6907
+ ...options,
6908
+ });
6909
+ this.name = "LlmRateLimitError";
6910
+ }
6911
+ }
6912
+ /**
6913
+ * Provider quota is exhausted or the account cannot be billed. Terminal and
6914
+ * actionable. `reason` distinguishes an exhausted usage quota from insufficient
6915
+ * funds.
6916
+ */
6917
+ class LlmQuotaError extends LlmError {
6918
+ constructor(message, { reason = "quota", ...options } = {}) {
6919
+ super(message, exports.ErrorCategory.Quota, {
6920
+ status: 402,
6921
+ title: "Payment Required",
6922
+ ...options,
6923
+ });
6924
+ this.name = "LlmQuotaError";
6925
+ this.reason = reason;
6926
+ }
6927
+ }
6928
+ /**
6929
+ * The request cannot be recovered from (bad request, authentication,
6930
+ * permission, not found). Terminal.
6931
+ */
6932
+ class LlmUnrecoverableError extends LlmError {
6933
+ constructor(message, options = {}) {
6934
+ super(message, exports.ErrorCategory.Unrecoverable, {
6935
+ status: 502,
6936
+ title: "Bad Gateway",
6937
+ ...options,
6938
+ });
6939
+ this.name = "LlmUnrecoverableError";
6940
+ }
6941
+ }
6942
+ /**
6943
+ * A transient or unknown error survived the retry budget. Terminal only because
6944
+ * retries were exhausted; the underlying condition may succeed later.
6945
+ */
6946
+ class LlmTransientError extends LlmError {
6947
+ constructor(message, options = {}) {
6948
+ super(message, exports.ErrorCategory.Retryable, {
6949
+ status: 504,
6950
+ title: "Gateway Timeout",
6951
+ ...options,
6952
+ });
6953
+ this.name = "LlmTransientError";
6954
+ }
6955
+ }
6956
+
6957
+ /**
6958
+ * Map a {@link ClassifiedError} to the matching {@link LlmError} subclass,
6959
+ * preserving the original error as `cause`. Used by the retry layer to throw a
6960
+ * stable, catchable type instead of a bare gateway error.
6961
+ */
6962
+ function toLlmError(classified, context = {}) {
6963
+ const original = classified.error;
6964
+ const message = original instanceof Error ? original.message : String(original);
6965
+ const options = {
6966
+ cause: original,
6967
+ model: context.model,
6968
+ provider: context.provider,
6969
+ retryAfterMs: classified.suggestedDelayMs,
6970
+ };
6971
+ switch (classified.category) {
6972
+ case exports.ErrorCategory.RateLimit:
6973
+ return new LlmRateLimitError(message, options);
6974
+ case exports.ErrorCategory.Quota:
6975
+ return new LlmQuotaError(message, {
6976
+ ...options,
6977
+ reason: classified.reason ?? "quota",
6978
+ });
6979
+ case exports.ErrorCategory.Unrecoverable:
6980
+ return new LlmUnrecoverableError(message, options);
6981
+ case exports.ErrorCategory.Retryable:
6982
+ case exports.ErrorCategory.Unknown:
6983
+ default:
6984
+ return new LlmTransientError(message, options);
6985
+ }
6986
+ }
6987
+
6495
6988
  //
6496
6989
  //
6497
6990
  // Main
@@ -6506,6 +6999,18 @@ class RetryExecutor {
6506
6999
  this.hookRunner = config.hookRunner ?? hookRunner;
6507
7000
  this.errorClassifier = config.errorClassifier;
6508
7001
  }
7002
+ /**
7003
+ * Build the typed, provider-agnostic error thrown when a request cannot be
7004
+ * completed — classified (rate limit / quota / unrecoverable / transient)
7005
+ * and carrying the provider, model, and original error as `cause`.
7006
+ */
7007
+ toTerminalError(error, context) {
7008
+ const classified = this.errorClassifier.classify(error);
7009
+ return toLlmError(classified, {
7010
+ model: context.model,
7011
+ provider: context.provider,
7012
+ });
7013
+ }
6509
7014
  /**
6510
7015
  * Execute an operation with retry logic.
6511
7016
  * Each attempt receives an AbortSignal. On failure, the signal is aborted
@@ -6549,8 +7054,7 @@ class RetryExecutor {
6549
7054
  providerRequest: options.context.providerRequest,
6550
7055
  error,
6551
7056
  });
6552
- const errorMessage = error instanceof Error ? error.message : String(error);
6553
- throw new errors.BadGatewayError(errorMessage);
7057
+ throw this.toTerminalError(error, options.context);
6554
7058
  }
6555
7059
  // Check if error is not retryable
6556
7060
  if (!this.errorClassifier.isRetryable(error)) {
@@ -6562,8 +7066,7 @@ class RetryExecutor {
6562
7066
  providerRequest: options.context.providerRequest,
6563
7067
  error,
6564
7068
  });
6565
- const errorMessage = error instanceof Error ? error.message : String(error);
6566
- throw new errors.BadGatewayError(errorMessage);
7069
+ throw this.toTerminalError(error, options.context);
6567
7070
  }
6568
7071
  // Warn if this is an unknown error type
6569
7072
  if (!this.errorClassifier.isKnownError(error)) {
@@ -6606,6 +7109,7 @@ const MAX_CONSECUTIVE_TOOL_ERRORS = 6;
6606
7109
  */
6607
7110
  function createErrorClassifier(adapter) {
6608
7111
  return {
7112
+ classify: (error) => adapter.classifyError(error),
6609
7113
  isRetryable: (error) => adapter.isRetryableError(error),
6610
7114
  isKnownError: (error) => {
6611
7115
  const classified = adapter.classifyError(error);
@@ -6675,6 +7179,7 @@ class OperateLoop {
6675
7179
  }
6676
7180
  // Rebuild request with updated history for next turn
6677
7181
  request = {
7182
+ effort: options.effort,
6678
7183
  format: state.formattedFormat,
6679
7184
  instructions: options.instructions,
6680
7185
  messages: state.currentInput,
@@ -6778,6 +7283,7 @@ class OperateLoop {
6778
7283
  }
6779
7284
  buildInitialRequest(state, options) {
6780
7285
  return {
7286
+ effort: options.effort,
6781
7287
  format: state.formattedFormat,
6782
7288
  instructions: options.instructions,
6783
7289
  messages: state.currentInput,
@@ -6857,7 +7363,9 @@ class OperateLoop {
6857
7363
  const response = await retryExecutor.execute((signal) => this.adapter.executeRequest(this.client, providerRequest, signal), {
6858
7364
  context: {
6859
7365
  input: state.currentInput,
7366
+ model: options.model ?? this.adapter.defaultModel,
6860
7367
  options,
7368
+ provider: this.adapter.name,
6861
7369
  providerRequest,
6862
7370
  },
6863
7371
  hooks: hooksWithProgress,
@@ -7292,6 +7800,7 @@ class StreamLoop {
7292
7800
  }
7293
7801
  // Rebuild request with updated history for next turn
7294
7802
  request = {
7803
+ effort: options.effort,
7295
7804
  format: state.formattedFormat,
7296
7805
  instructions: options.instructions,
7297
7806
  messages: state.currentInput,
@@ -7367,6 +7876,7 @@ class StreamLoop {
7367
7876
  }
7368
7877
  buildInitialRequest(state, options) {
7369
7878
  return {
7879
+ effort: options.effort,
7370
7880
  format: state.formattedFormat,
7371
7881
  instructions: options.instructions,
7372
7882
  messages: state.currentInput,
@@ -7478,9 +7988,11 @@ class StreamLoop {
7478
7988
  !this.adapter.isRetryableError(error)) {
7479
7989
  log.error(`Stream request failed after ${this.retryPolicy.maxRetries} retries`);
7480
7990
  log.var({ error });
7481
- const errorMessage = error instanceof Error ? error.message : String(error);
7482
7991
  llmSpan?.finish();
7483
- throw new errors.BadGatewayError(errorMessage);
7992
+ throw toLlmError(this.adapter.classifyError(error), {
7993
+ model: options.model ?? this.adapter.defaultModel,
7994
+ provider: this.adapter.name,
7995
+ });
7484
7996
  }
7485
7997
  const delay = this.retryPolicy.getDelayForAttempt(attempt);
7486
7998
  log.warn(`Stream request failed. Retrying in ${delay}ms...`);
@@ -7930,7 +8442,7 @@ async function createStructuredCompletion$1(client, messages, model, responseSch
7930
8442
 
7931
8443
  // Main class implementation
7932
8444
  class AnthropicProvider {
7933
- constructor(model = PROVIDER.ANTHROPIC.MODEL.DEFAULT, { apiKey } = {}) {
8445
+ constructor(model = PROVIDER.ANTHROPIC.DEFAULT, { apiKey } = {}) {
7934
8446
  this.log = getLogger$5();
7935
8447
  this.conversationHistory = [];
7936
8448
  this.model = model;
@@ -8038,7 +8550,7 @@ async function initializeClient$4({ region, } = {}) {
8038
8550
  }
8039
8551
 
8040
8552
  class BedrockProvider {
8041
- constructor(model = PROVIDER.BEDROCK.MODEL.DEFAULT, { region } = {}) {
8553
+ constructor(model = PROVIDER.BEDROCK.DEFAULT, { region } = {}) {
8042
8554
  this.log = getLogger$4();
8043
8555
  this.conversationHistory = [];
8044
8556
  this.model = model;
@@ -8281,7 +8793,7 @@ function prepareMessages$2(message, { data, placeholders } = {}) {
8281
8793
  }
8282
8794
 
8283
8795
  class GoogleProvider {
8284
- constructor(model = PROVIDER.GOOGLE.MODEL.DEFAULT, { apiKey } = {}) {
8796
+ constructor(model = PROVIDER.GOOGLE.DEFAULT, { apiKey } = {}) {
8285
8797
  this.log = getLogger$3();
8286
8798
  this.conversationHistory = [];
8287
8799
  this.model = model;
@@ -8483,7 +8995,7 @@ async function createTextCompletion(client, { messages, model, }) {
8483
8995
  }
8484
8996
 
8485
8997
  class OpenAiProvider {
8486
- constructor(model = PROVIDER.OPENAI.MODEL.DEFAULT, { apiKey } = {}) {
8998
+ constructor(model = PROVIDER.OPENAI.DEFAULT, { apiKey } = {}) {
8487
8999
  this.log = getLogger$2();
8488
9000
  this.conversationHistory = [];
8489
9001
  this.model = model;
@@ -8714,7 +9226,7 @@ async function initializeClient$1({ apiKey, } = {}) {
8714
9226
  }
8715
9227
  // Get default model from environment or constants
8716
9228
  function getDefaultModel() {
8717
- return process.env.OPENROUTER_MODEL || PROVIDER.OPENROUTER.MODEL.DEFAULT;
9229
+ return process.env.OPENROUTER_MODEL || PROVIDER.OPENROUTER.DEFAULT;
8718
9230
  }
8719
9231
  function formatSystemMessage(systemPrompt, { data, placeholders } = {}) {
8720
9232
  const content = placeholders?.system === false
@@ -8866,7 +9378,7 @@ async function initializeClient({ apiKey, } = {}) {
8866
9378
  }
8867
9379
 
8868
9380
  class XaiProvider {
8869
- constructor(model = PROVIDER.XAI.MODEL.DEFAULT, { apiKey } = {}) {
9381
+ constructor(model = PROVIDER.XAI.DEFAULT, { apiKey } = {}) {
8870
9382
  this.log = getLogger$2();
8871
9383
  this.conversationHistory = [];
8872
9384
  this.model = model;
@@ -8951,7 +9463,14 @@ class XaiProvider {
8951
9463
 
8952
9464
  class Llm {
8953
9465
  constructor(providerName = DEFAULT.PROVIDER.NAME, options = {}) {
8954
- const { fallback, model } = options;
9466
+ const { fallback: fallbackOption, model: modelOption } = options;
9467
+ // A `model` array is sugar for a preference-ordered fallback chain:
9468
+ // index 0 is primary, the rest become fallback entries (provider
9469
+ // auto-detected). Any explicit `fallback` is appended after the chain.
9470
+ const { fallback: modelFallback, model } = resolveModelChain(modelOption);
9471
+ const fallback = modelFallback.length || fallbackOption
9472
+ ? [...modelFallback, ...(fallbackOption ?? [])]
9473
+ : undefined;
8955
9474
  let finalProvider = providerName;
8956
9475
  let finalModel = model;
8957
9476
  // Legacy: accept "gemini" but warn
@@ -8997,23 +9516,25 @@ class Llm {
8997
9516
  const { apiKey, model } = options;
8998
9517
  switch (providerName) {
8999
9518
  case PROVIDER.ANTHROPIC.NAME:
9000
- return new AnthropicProvider(model || PROVIDER.ANTHROPIC.MODEL.DEFAULT, { apiKey });
9519
+ return new AnthropicProvider(model || PROVIDER.ANTHROPIC.DEFAULT, {
9520
+ apiKey,
9521
+ });
9001
9522
  case PROVIDER.BEDROCK.NAME:
9002
- return new BedrockProvider(model || PROVIDER.BEDROCK.MODEL.DEFAULT);
9523
+ return new BedrockProvider(model || PROVIDER.BEDROCK.DEFAULT);
9003
9524
  case PROVIDER.GOOGLE.NAME:
9004
- return new GoogleProvider(model || PROVIDER.GOOGLE.MODEL.DEFAULT, {
9525
+ return new GoogleProvider(model || PROVIDER.GOOGLE.DEFAULT, {
9005
9526
  apiKey,
9006
9527
  });
9007
9528
  case PROVIDER.OPENAI.NAME:
9008
- return new OpenAiProvider(model || PROVIDER.OPENAI.MODEL.DEFAULT, {
9529
+ return new OpenAiProvider(model || PROVIDER.OPENAI.DEFAULT, {
9009
9530
  apiKey,
9010
9531
  });
9011
9532
  case PROVIDER.OPENROUTER.NAME:
9012
- return new OpenRouterProvider(model || PROVIDER.OPENROUTER.MODEL.DEFAULT, {
9533
+ return new OpenRouterProvider(model || PROVIDER.OPENROUTER.DEFAULT, {
9013
9534
  apiKey,
9014
9535
  });
9015
9536
  case PROVIDER.XAI.NAME:
9016
- return new XaiProvider(model || PROVIDER.XAI.MODEL.DEFAULT, {
9537
+ return new XaiProvider(model || PROVIDER.XAI.DEFAULT, {
9017
9538
  apiKey,
9018
9539
  });
9019
9540
  default:
@@ -9021,6 +9542,11 @@ class Llm {
9021
9542
  }
9022
9543
  }
9023
9544
  async send(message, options) {
9545
+ if (options && Array.isArray(options.model)) {
9546
+ // `send` has no fallback machinery; use the primary model only
9547
+ const { model } = resolveModelChain(options.model);
9548
+ return this._llm.send(message, { ...options, model });
9549
+ }
9024
9550
  return this._llm.send(message, options);
9025
9551
  }
9026
9552
  /**
@@ -9053,8 +9579,21 @@ class Llm {
9053
9579
  if (!this._llm.operate) {
9054
9580
  throw new errors.NotImplementedError(`Provider ${this._provider} does not support operate method`);
9055
9581
  }
9056
- const fallbackChain = this.resolveFallbackChain(options);
9057
- const optionsWithoutFallback = { ...options, fallback: false };
9582
+ // A per-call `model` array becomes primary + a derived fallback chain
9583
+ // prepended to any resolved chain.
9584
+ const { fallback: modelFallback, model: perCallModel } = resolveModelChain(options.model);
9585
+ const resolvedOptions = {
9586
+ ...options,
9587
+ model: perCallModel,
9588
+ };
9589
+ const fallbackChain = [
9590
+ ...modelFallback,
9591
+ ...this.resolveFallbackChain(resolvedOptions),
9592
+ ];
9593
+ const optionsWithoutFallback = {
9594
+ ...resolvedOptions,
9595
+ fallback: false,
9596
+ };
9058
9597
  let lastError;
9059
9598
  let attempts = 0;
9060
9599
  // Try primary provider first
@@ -9103,33 +9642,45 @@ class Llm {
9103
9642
  if (!this._llm.stream) {
9104
9643
  throw new errors.NotImplementedError(`Provider ${this._provider} does not support stream method`);
9105
9644
  }
9106
- yield* this._llm.stream(input, options);
9645
+ // `stream` has no instance-level fallback machinery; an array model uses
9646
+ // the primary and ignores the rest.
9647
+ const { model } = resolveModelChain(options.model);
9648
+ const streamOptions = { ...options, model };
9649
+ yield* this._llm.stream(input, streamOptions);
9107
9650
  }
9108
9651
  static async send(message, options) {
9109
9652
  const { llm, apiKey, model, ...messageOptions } = options || {};
9110
- const instance = new Llm(llm, { apiKey, model });
9653
+ // A `model` array derives a fallback chain; `send` has no fallback
9654
+ // machinery, so the primary model is used and the chain is ignored.
9655
+ const { model: primaryModel } = resolveModelChain(model);
9656
+ const instance = new Llm(llm, { apiKey, model: primaryModel });
9111
9657
  return instance.send(message, messageOptions);
9112
9658
  }
9113
9659
  static async operate(input, options) {
9114
9660
  const { apiKey, fallback, llm, model, ...operateOptions } = options || {};
9661
+ // A `model` array becomes primary + derived fallback chain
9662
+ const { fallback: modelFallback, model: primaryModel } = resolveModelChain(model);
9115
9663
  let finalLlm = llm;
9116
- let finalModel = model;
9117
- if (!llm && model) {
9118
- const determined = determineModelProvider(model);
9664
+ let finalModel = primaryModel;
9665
+ if (!llm && primaryModel) {
9666
+ const determined = determineModelProvider(primaryModel);
9119
9667
  if (determined.provider) {
9120
9668
  finalLlm = determined.provider;
9121
9669
  }
9122
9670
  }
9123
- else if (llm && model) {
9671
+ else if (llm && primaryModel) {
9124
9672
  // When both llm and model are provided, check if they conflict
9125
- const determined = determineModelProvider(model);
9673
+ const determined = determineModelProvider(primaryModel);
9126
9674
  if (determined.provider && determined.provider !== llm) {
9127
9675
  // Don't pass the conflicting model to the constructor
9128
9676
  finalModel = undefined;
9129
9677
  }
9130
9678
  }
9131
9679
  // Resolve fallback for static method: pass to instance if array, pass to operate options if false
9132
- const instanceFallback = Array.isArray(fallback) ? fallback : undefined;
9680
+ const explicitFallback = Array.isArray(fallback) ? fallback : [];
9681
+ const instanceFallback = modelFallback.length || explicitFallback.length
9682
+ ? [...modelFallback, ...explicitFallback]
9683
+ : undefined;
9133
9684
  const operateFallback = fallback === false ? false : undefined;
9134
9685
  const instance = new Llm(finalLlm, {
9135
9686
  apiKey,
@@ -9143,23 +9694,29 @@ class Llm {
9143
9694
  }
9144
9695
  static stream(input, options) {
9145
9696
  const { llm, apiKey, model, ...streamOptions } = options || {};
9697
+ // A `model` array becomes primary + derived fallback chain
9698
+ const { fallback: modelFallback, model: primaryModel } = resolveModelChain(model);
9146
9699
  let finalLlm = llm;
9147
- let finalModel = model;
9148
- if (!llm && model) {
9149
- const determined = determineModelProvider(model);
9700
+ let finalModel = primaryModel;
9701
+ if (!llm && primaryModel) {
9702
+ const determined = determineModelProvider(primaryModel);
9150
9703
  if (determined.provider) {
9151
9704
  finalLlm = determined.provider;
9152
9705
  }
9153
9706
  }
9154
- else if (llm && model) {
9707
+ else if (llm && primaryModel) {
9155
9708
  // When both llm and model are provided, check if they conflict
9156
- const determined = determineModelProvider(model);
9709
+ const determined = determineModelProvider(primaryModel);
9157
9710
  if (determined.provider && determined.provider !== llm) {
9158
9711
  // Don't pass the conflicting model to the constructor
9159
9712
  finalModel = undefined;
9160
9713
  }
9161
9714
  }
9162
- const instance = new Llm(finalLlm, { apiKey, model: finalModel });
9715
+ const instance = new Llm(finalLlm, {
9716
+ apiKey,
9717
+ fallback: modelFallback.length ? modelFallback : undefined,
9718
+ model: finalModel,
9719
+ });
9163
9720
  return instance.stream(input, streamOptions);
9164
9721
  }
9165
9722
  }
@@ -9463,6 +10020,11 @@ exports.GoogleProvider = GoogleProvider;
9463
10020
  exports.JaypieToolkit = JaypieToolkit;
9464
10021
  exports.LLM = constants;
9465
10022
  exports.Llm = Llm;
10023
+ exports.LlmError = LlmError;
10024
+ exports.LlmQuotaError = LlmQuotaError;
10025
+ exports.LlmRateLimitError = LlmRateLimitError;
10026
+ exports.LlmTransientError = LlmTransientError;
10027
+ exports.LlmUnrecoverableError = LlmUnrecoverableError;
9466
10028
  exports.OpenRouterProvider = OpenRouterProvider;
9467
10029
  exports.Toolkit = Toolkit;
9468
10030
  exports.XaiProvider = XaiProvider;