@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
package/dist/esm/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { ConfigurationError, BadGatewayError, TooManyRequestsError, NotImplementedError } from '@jaypie/errors';
1
+ import { ConfigurationError, JaypieError, BadGatewayError, TooManyRequestsError, NotImplementedError } from '@jaypie/errors';
2
2
  import log$1, { log as log$2 } from '@jaypie/logger';
3
3
  import { z } from 'zod/v4';
4
4
  import { placeholders, JAYPIE, resolveValue, sleep } from '@jaypie/kit';
@@ -11,35 +11,21 @@ import { resolve } from 'path';
11
11
  import { getS3FileBuffer, getEnvSecret } from '@jaypie/aws';
12
12
  import { fetchWeatherApi } from 'openmeteo';
13
13
 
14
- const FIRST_CLASS_PROVIDER = {
15
- // https://docs.anthropic.com/en/docs/about-claude/models/overview
16
- ANTHROPIC: {
17
- DEFAULT: "claude-sonnet-4-6",
18
- LARGE: "claude-opus-4-8",
19
- SMALL: "claude-sonnet-4-6",
20
- TINY: "claude-haiku-4-5",
21
- },
22
- // https://ai.google.dev/gemini-api/docs/models
23
- GOOGLE: {
24
- DEFAULT: "gemini-3.1-pro-preview",
25
- LARGE: "gemini-3.1-pro-preview",
26
- SMALL: "gemini-3.5-flash",
27
- TINY: "gemini-3.1-flash-lite",
28
- },
29
- // https://developers.openai.com/api/docs/models
30
- OPENAI: {
31
- DEFAULT: "gpt-5.4",
32
- LARGE: "gpt-5.5",
33
- SMALL: "gpt-5.4-mini",
34
- TINY: "gpt-5.4-nano",
35
- },
36
- // https://docs.x.ai/developers/models
37
- XAI: {
38
- DEFAULT: "grok-latest",
39
- LARGE: "grok-latest",
40
- SMALL: "grok-4-1-fast-reasoning",
41
- TINY: "grok-4-1-fast-non-reasoning",
42
- },
14
+ /**
15
+ * Provider-neutral reasoning-effort levels — a five-point relative scale that
16
+ * deliberately borrows no provider's vocabulary. Each adapter translates these
17
+ * to its provider's native control (OpenAI `reasoning.effort`, Anthropic
18
+ * `output_config.effort`, Gemini `thinkingLevel`/`thinkingBudget`, Grok
19
+ * `reasoning_effort`, OpenRouter `reasoning.effort`), spreading the scale across
20
+ * the provider's available range. Omitting `effort` leaves the provider default
21
+ * untouched, so it is safe to set across a fallback chain.
22
+ */
23
+ const EFFORT = {
24
+ LOWEST: "lowest",
25
+ LOW: "low",
26
+ MEDIUM: "medium",
27
+ HIGH: "high",
28
+ HIGHEST: "highest",
43
29
  };
44
30
  const MODEL = {
45
31
  // Anthropic
@@ -53,19 +39,33 @@ const MODEL = {
53
39
  GEMINI_FLASH_LITE: "gemini-3.1-flash-lite",
54
40
  GEMINI_PRO: "gemini-3.1-pro-preview",
55
41
  // OpenAI
42
+ SOL: "gpt-5.6-sol",
43
+ TERRA: "gpt-5.6-terra",
44
+ LUNA: "gpt-5.6-luna",
45
+ /** @deprecated use MODEL.SOL (gpt-5.6-sol) */
56
46
  GPT: "gpt-5.5",
47
+ /** @deprecated use MODEL.TERRA (gpt-5.6-terra) */
57
48
  GPT_MINI: "gpt-5.4-mini",
49
+ /** @deprecated use MODEL.LUNA (gpt-5.6-luna) */
58
50
  GPT_NANO: "gpt-5.4-nano",
59
51
  // xAI
60
52
  GROK: "grok-latest",
53
+ // OpenRouter (provider-prefixed routes; traversed by the OpenRouter hot test)
54
+ OPENROUTER: {
55
+ GLM: "z-ai/glm-5.2",
56
+ LUNA: "openai/gpt-5.6-luna",
57
+ SONNET: "anthropic/claude-sonnet-5",
58
+ },
61
59
  };
62
60
  const GOOGLE_PROVIDER = {
63
61
  // https://ai.google.dev/gemini-api/docs/models
62
+ DEFAULT: MODEL.GEMINI_FLASH,
63
+ /** @deprecated Size tiers are retired in 2.0. Use PROVIDER.GOOGLE.DEFAULT, or pick a specific model from MODEL.*. */
64
64
  MODEL: {
65
- DEFAULT: FIRST_CLASS_PROVIDER.GOOGLE.DEFAULT,
66
- LARGE: FIRST_CLASS_PROVIDER.GOOGLE.LARGE,
67
- SMALL: FIRST_CLASS_PROVIDER.GOOGLE.SMALL,
68
- TINY: FIRST_CLASS_PROVIDER.GOOGLE.TINY,
65
+ DEFAULT: "gemini-3.1-pro-preview",
66
+ LARGE: "gemini-3.1-pro-preview",
67
+ SMALL: "gemini-3.5-flash",
68
+ TINY: "gemini-3.1-flash-lite",
69
69
  },
70
70
  MODEL_MATCH_WORDS: ["gemini", "google"],
71
71
  NAME: "google",
@@ -77,6 +77,10 @@ const GOOGLE_PROVIDER = {
77
77
  const PROVIDER = {
78
78
  // https://docs.aws.amazon.com/bedrock/latest/userguide/models-supported.html
79
79
  BEDROCK: {
80
+ // Bedrock has no MODEL.* catalog entry yet; keep the literal default id.
81
+ // nova-pro is the Amazon-native model that reliably does tools+structured.
82
+ DEFAULT: "amazon.nova-pro-v1:0",
83
+ /** @deprecated Size tiers are retired in 2.0. Use PROVIDER.BEDROCK.DEFAULT. */
80
84
  MODEL: {
81
85
  DEFAULT: "amazon.nova-lite-v1:0",
82
86
  LARGE: "amazon.nova-pro-v1:0",
@@ -97,22 +101,26 @@ const PROVIDER = {
97
101
  },
98
102
  ANTHROPIC: {
99
103
  // https://docs.anthropic.com/en/docs/about-claude/models/overview
104
+ DEFAULT: MODEL.SONNET,
100
105
  MAX_TOKENS: {
101
106
  // Non-streaming ceiling: responses above ~16K output tokens risk HTTP
102
107
  // timeouts; streaming requests resolve to the model maximum instead
103
108
  // (see util/maxOutputTokens.ts)
104
109
  DEFAULT: 16384,
105
110
  },
111
+ /** @deprecated Size tiers are retired in 2.0. Use PROVIDER.ANTHROPIC.DEFAULT, or pick a specific model from MODEL.*. */
106
112
  MODEL: {
107
- DEFAULT: FIRST_CLASS_PROVIDER.ANTHROPIC.DEFAULT,
108
- LARGE: FIRST_CLASS_PROVIDER.ANTHROPIC.LARGE,
109
- SMALL: FIRST_CLASS_PROVIDER.ANTHROPIC.SMALL,
110
- TINY: FIRST_CLASS_PROVIDER.ANTHROPIC.TINY,
113
+ DEFAULT: "claude-sonnet-4-6",
114
+ LARGE: "claude-opus-4-8",
115
+ SMALL: "claude-sonnet-4-6",
116
+ TINY: "claude-haiku-4-5",
111
117
  },
112
118
  MODEL_MATCH_WORDS: [
113
119
  "anthropic",
114
120
  "claude",
121
+ "fable",
115
122
  "haiku",
123
+ "mythos",
116
124
  "opus",
117
125
  "sonnet",
118
126
  ],
@@ -135,21 +143,25 @@ const PROVIDER = {
135
143
  GOOGLE: GOOGLE_PROVIDER,
136
144
  OPENAI: {
137
145
  // https://platform.openai.com/docs/models
146
+ DEFAULT: MODEL.SOL,
147
+ /** @deprecated Size tiers are retired in 2.0. Use PROVIDER.OPENAI.DEFAULT, or pick a specific model from MODEL.*. */
138
148
  MODEL: {
139
- DEFAULT: FIRST_CLASS_PROVIDER.OPENAI.DEFAULT,
140
- LARGE: FIRST_CLASS_PROVIDER.OPENAI.LARGE,
141
- SMALL: FIRST_CLASS_PROVIDER.OPENAI.SMALL,
142
- TINY: FIRST_CLASS_PROVIDER.OPENAI.TINY,
149
+ DEFAULT: "gpt-5.4",
150
+ LARGE: "gpt-5.5",
151
+ SMALL: "gpt-5.4-mini",
152
+ TINY: "gpt-5.4-nano",
143
153
  },
144
- MODEL_MATCH_WORDS: ["openai", "gpt", /^o\d/],
154
+ MODEL_MATCH_WORDS: ["gpt", "luna", "openai", "sol", "terra", /^o\d/],
145
155
  NAME: "openai",
146
156
  },
147
157
  OPENROUTER: {
158
+ DEFAULT: MODEL.OPENROUTER.SONNET,
159
+ /** @deprecated Size tiers are retired in 2.0. Use PROVIDER.OPENROUTER.DEFAULT, or pick a specific route from MODEL.OPENROUTER.*. */
148
160
  MODEL: {
149
- DEFAULT: `anthropic/${FIRST_CLASS_PROVIDER.ANTHROPIC.DEFAULT}`,
150
- LARGE: `anthropic/${FIRST_CLASS_PROVIDER.ANTHROPIC.LARGE}`,
151
- SMALL: `anthropic/${FIRST_CLASS_PROVIDER.ANTHROPIC.SMALL}`,
152
- TINY: `anthropic/${FIRST_CLASS_PROVIDER.ANTHROPIC.TINY}`,
161
+ DEFAULT: "anthropic/claude-sonnet-4-6",
162
+ LARGE: "anthropic/claude-opus-4-8",
163
+ SMALL: "anthropic/claude-sonnet-4-6",
164
+ TINY: "anthropic/claude-haiku-4-5",
153
165
  },
154
166
  MODEL_MATCH_WORDS: ["openrouter"],
155
167
  NAME: "openrouter",
@@ -164,11 +176,13 @@ const PROVIDER = {
164
176
  // https://docs.x.ai/docs/models
165
177
  API_KEY: "XAI_API_KEY",
166
178
  BASE_URL: "https://api.x.ai/v1",
179
+ DEFAULT: MODEL.GROK,
180
+ /** @deprecated Size tiers are retired in 2.0. Use PROVIDER.XAI.DEFAULT, or pick a specific model from MODEL.*. */
167
181
  MODEL: {
168
- DEFAULT: FIRST_CLASS_PROVIDER.XAI.DEFAULT,
169
- LARGE: FIRST_CLASS_PROVIDER.XAI.LARGE,
170
- SMALL: FIRST_CLASS_PROVIDER.XAI.SMALL,
171
- TINY: FIRST_CLASS_PROVIDER.XAI.TINY,
182
+ DEFAULT: "grok-latest",
183
+ LARGE: "grok-latest",
184
+ SMALL: "grok-4-1-fast-reasoning",
185
+ TINY: "grok-4-1-fast-non-reasoning",
172
186
  },
173
187
  MODEL_MATCH_WORDS: ["grok", "xai"],
174
188
  NAME: "xai",
@@ -176,6 +190,7 @@ const PROVIDER = {
176
190
  };
177
191
  // Last: Defaults
178
192
  const DEFAULT = {
193
+ /** @deprecated Size tiers are retired in 2.0. Use DEFAULT.PROVIDER.DEFAULT, or pick a specific model from MODEL.*. */
179
194
  MODEL: {
180
195
  BASE: PROVIDER.OPENAI.MODEL.DEFAULT,
181
196
  LARGE: PROVIDER.OPENAI.MODEL.LARGE,
@@ -184,6 +199,10 @@ const DEFAULT = {
184
199
  },
185
200
  PROVIDER: PROVIDER.OPENAI,
186
201
  };
202
+ /**
203
+ * @deprecated Size-tier catalogs are retired in 2.0. Pick specific models from
204
+ * MODEL.* (grouped by provider via MODEL_MATCH_WORDS / determineModelProvider).
205
+ */
187
206
  // Only include "first class" models, not OpenRouter or other proxy services
188
207
  const ALL = {
189
208
  BASE: [
@@ -236,6 +255,7 @@ var constants = /*#__PURE__*/Object.freeze({
236
255
  __proto__: null,
237
256
  ALL: ALL,
238
257
  DEFAULT: DEFAULT,
258
+ EFFORT: EFFORT,
239
259
  MODEL: MODEL,
240
260
  PROVIDER: PROVIDER
241
261
  });
@@ -243,7 +263,7 @@ var constants = /*#__PURE__*/Object.freeze({
243
263
  function determineModelProvider(input) {
244
264
  if (!input) {
245
265
  return {
246
- model: DEFAULT.PROVIDER.MODEL.DEFAULT,
266
+ model: DEFAULT.PROVIDER.DEFAULT,
247
267
  provider: DEFAULT.PROVIDER.NAME,
248
268
  };
249
269
  }
@@ -266,85 +286,42 @@ function determineModelProvider(input) {
266
286
  // Check if input is a provider name
267
287
  if (input === PROVIDER.BEDROCK.NAME) {
268
288
  return {
269
- model: PROVIDER.BEDROCK.MODEL.DEFAULT,
289
+ model: PROVIDER.BEDROCK.DEFAULT,
270
290
  provider: PROVIDER.BEDROCK.NAME,
271
291
  };
272
292
  }
273
293
  if (input === PROVIDER.ANTHROPIC.NAME) {
274
294
  return {
275
- model: PROVIDER.ANTHROPIC.MODEL.DEFAULT,
295
+ model: PROVIDER.ANTHROPIC.DEFAULT,
276
296
  provider: PROVIDER.ANTHROPIC.NAME,
277
297
  };
278
298
  }
279
299
  if (input === PROVIDER.GOOGLE.NAME || input === "gemini") {
280
300
  return {
281
- model: PROVIDER.GOOGLE.MODEL.DEFAULT,
301
+ model: PROVIDER.GOOGLE.DEFAULT,
282
302
  provider: PROVIDER.GOOGLE.NAME,
283
303
  };
284
304
  }
285
305
  if (input === PROVIDER.OPENAI.NAME) {
286
306
  return {
287
- model: PROVIDER.OPENAI.MODEL.DEFAULT,
307
+ model: PROVIDER.OPENAI.DEFAULT,
288
308
  provider: PROVIDER.OPENAI.NAME,
289
309
  };
290
310
  }
291
311
  if (input === PROVIDER.OPENROUTER.NAME) {
292
312
  return {
293
- model: PROVIDER.OPENROUTER.MODEL.DEFAULT,
313
+ model: PROVIDER.OPENROUTER.DEFAULT,
294
314
  provider: PROVIDER.OPENROUTER.NAME,
295
315
  };
296
316
  }
297
317
  if (input === PROVIDER.XAI.NAME) {
298
318
  return {
299
- model: PROVIDER.XAI.MODEL.DEFAULT,
319
+ model: PROVIDER.XAI.DEFAULT,
300
320
  provider: PROVIDER.XAI.NAME,
301
321
  };
302
322
  }
303
- // Check if input matches an Anthropic model exactly
304
- for (const [, modelValue] of Object.entries(PROVIDER.ANTHROPIC.MODEL)) {
305
- if (input === modelValue) {
306
- return {
307
- model: input,
308
- provider: PROVIDER.ANTHROPIC.NAME,
309
- };
310
- }
311
- }
312
- // Check if input matches a Gemini model exactly
313
- for (const [, modelValue] of Object.entries(PROVIDER.GOOGLE.MODEL)) {
314
- if (input === modelValue) {
315
- return {
316
- model: input,
317
- provider: PROVIDER.GOOGLE.NAME,
318
- };
319
- }
320
- }
321
- // Check if input matches an OpenAI model exactly
322
- for (const [, modelValue] of Object.entries(PROVIDER.OPENAI.MODEL)) {
323
- if (input === modelValue) {
324
- return {
325
- model: input,
326
- provider: PROVIDER.OPENAI.NAME,
327
- };
328
- }
329
- }
330
- // Check if input matches an OpenRouter model exactly
331
- for (const [, modelValue] of Object.entries(PROVIDER.OPENROUTER.MODEL)) {
332
- if (input === modelValue) {
333
- return {
334
- model: input,
335
- provider: PROVIDER.OPENROUTER.NAME,
336
- };
337
- }
338
- }
339
- // Check if input matches an xAI model exactly
340
- for (const [, modelValue] of Object.entries(PROVIDER.XAI.MODEL)) {
341
- if (input === modelValue) {
342
- return {
343
- model: input,
344
- provider: PROVIDER.XAI.NAME,
345
- };
346
- }
347
- }
323
+ // Exact model ids are classified by the "/" rule and MODEL_MATCH_WORDS below,
324
+ // so no per-provider id catalog is consulted here.
348
325
  // Assume OpenRouter for models containing "/" (e.g., "openai/gpt-4", "anthropic/claude-3-opus")
349
326
  // This check must come before match words so that "openai/gpt-4" is not matched by "openai" keyword
350
327
  if (input.includes("/")) {
@@ -424,6 +401,36 @@ function determineModelProvider(input) {
424
401
  };
425
402
  }
426
403
 
404
+ /**
405
+ * Normalizes a `model` option that may be a single model name or a
406
+ * preference-ordered array of model names.
407
+ *
408
+ * A `string[]` is interpreted as a fallback chain: index `0` is the primary
409
+ * model, indices `1..` become fallback entries with an auto-detected provider
410
+ * (reusing `determineModelProvider`, exactly as the constructor's first
411
+ * argument does).
412
+ *
413
+ * Returns the primary model plus the derived fallback chain. A bare string
414
+ * yields an empty chain and is unchanged.
415
+ */
416
+ function resolveModelChain(model) {
417
+ if (!Array.isArray(model)) {
418
+ return { fallback: [], model };
419
+ }
420
+ if (model.length === 0) {
421
+ throw new ConfigurationError("model array must contain at least one model name");
422
+ }
423
+ const [primary, ...rest] = model;
424
+ const fallback = rest.map((name) => {
425
+ const determined = determineModelProvider(name);
426
+ if (!determined.provider) {
427
+ throw new ConfigurationError(`Unable to determine provider from model: ${name}`);
428
+ }
429
+ return { model: determined.model, provider: determined.provider };
430
+ });
431
+ return { fallback, model: primary };
432
+ }
433
+
427
434
  //
428
435
  //
429
436
  // Abstract Base Adapter
@@ -461,6 +468,125 @@ class BaseProviderAdapter {
461
468
  }
462
469
  }
463
470
 
471
+ /**
472
+ * Per-provider translation of the provider-neutral {@link LlmEffort} scale.
473
+ *
474
+ * The neutral enum (lowest → low → medium → high → highest) is a relative
475
+ * five-point scale. Each table below maps it onto the target provider's native
476
+ * effort control, keeping `medium`/`high` semantically aligned across providers
477
+ * and using each provider's extra bottom (`minimal`) or top (`xhigh`/`max`)
478
+ * rung where one exists. Providers with fewer levels collapse the ends and mark
479
+ * the result `papered`. A single `effort` value is therefore safe to reuse
480
+ * across providers and fallback chains.
481
+ */
482
+ /** Consistent debug message for a papered-over effort level. */
483
+ function paperedEffortMessage({ model, provider, requested, value, }) {
484
+ return `[llm] effort '${requested}' has no distinct tier on ${provider} model '${model}'; using '${value}'`;
485
+ }
486
+ // OpenAI Responses API `reasoning.effort`.
487
+ // Full ladder: minimal | low | medium | high | xhigh (plus `none`). Availability
488
+ // is not uniform across the gpt-5 line:
489
+ // - `xhigh` was introduced at gpt-5.2 and has been continuous since, so it is
490
+ // safe for our gpt-5.4 default and everything newer.
491
+ // - `minimal` shipped on gpt-5/5.1, was dropped at gpt-5.2, and returned on
492
+ // the current line; because that history is non-monotonic we only trust it
493
+ // from gpt-5.4 (our default floor) onward.
494
+ // Outside those windows (older gpt-5, o-series) the extreme rung is clamped and
495
+ // the mapping reports `papered: true`.
496
+ const OPENAI_EFFORT = {
497
+ [EFFORT.LOWEST]: "minimal",
498
+ [EFFORT.LOW]: "low",
499
+ [EFFORT.MEDIUM]: "medium",
500
+ [EFFORT.HIGH]: "high",
501
+ [EFFORT.HIGHEST]: "xhigh",
502
+ };
503
+ function openAiGptVersion(model) {
504
+ const match = model.match(/^gpt-(\d+)(?:\.(\d+))?/);
505
+ if (!match)
506
+ return null;
507
+ return { major: Number(match[1]), minor: match[2] ? Number(match[2]) : 0 };
508
+ }
509
+ function atLeast(version, major, minor) {
510
+ if (!version)
511
+ return false;
512
+ return (version.major > major || (version.major === major && version.minor >= minor));
513
+ }
514
+ function toOpenAiEffort(effort, { model }) {
515
+ const native = OPENAI_EFFORT[effort];
516
+ const version = openAiGptVersion(model);
517
+ // `minimal` only from gpt-5.4 (non-monotonic history; absent on o-series)
518
+ if (native === "minimal" && !atLeast(version, 5, 4)) {
519
+ return { papered: true, value: "low" };
520
+ }
521
+ // `xhigh` from gpt-5.2 onward (absent on older gpt-5 and o-series)
522
+ if (native === "xhigh" && !atLeast(version, 5, 2)) {
523
+ return { papered: true, value: "high" };
524
+ }
525
+ return { papered: false, value: native };
526
+ }
527
+ // xAI Grok `reasoning_effort` — low | medium | high. No sub-low or top rung, so
528
+ // `lowest` collapses onto `low` and `highest` onto `high`.
529
+ const XAI_EFFORT = {
530
+ [EFFORT.LOWEST]: { papered: true, value: "low" },
531
+ [EFFORT.LOW]: { papered: false, value: "low" },
532
+ [EFFORT.MEDIUM]: { papered: false, value: "medium" },
533
+ [EFFORT.HIGH]: { papered: false, value: "high" },
534
+ [EFFORT.HIGHEST]: { papered: true, value: "high" },
535
+ };
536
+ function toXaiEffort(effort) {
537
+ return XAI_EFFORT[effort];
538
+ }
539
+ // Anthropic `output_config.effort` — low | medium | high | xhigh | max. No
540
+ // sub-low rung, so `lowest` collapses onto `low`; `highest` reaches `max`.
541
+ const ANTHROPIC_EFFORT = {
542
+ [EFFORT.LOWEST]: { papered: true, value: "low" },
543
+ [EFFORT.LOW]: { papered: false, value: "low" },
544
+ [EFFORT.MEDIUM]: { papered: false, value: "medium" },
545
+ [EFFORT.HIGH]: { papered: false, value: "high" },
546
+ [EFFORT.HIGHEST]: { papered: false, value: "max" },
547
+ };
548
+ function toAnthropicEffort(effort) {
549
+ return ANTHROPIC_EFFORT[effort];
550
+ }
551
+ // Gemini 3.x `thinkingConfig.thinkingLevel` — MINIMAL | LOW | MEDIUM | HIGH. No
552
+ // top rung above HIGH, so `highest` collapses onto HIGH.
553
+ const GEMINI_THINKING_LEVEL = {
554
+ [EFFORT.LOWEST]: { papered: false, value: "MINIMAL" },
555
+ [EFFORT.LOW]: { papered: false, value: "LOW" },
556
+ [EFFORT.MEDIUM]: { papered: false, value: "MEDIUM" },
557
+ [EFFORT.HIGH]: { papered: false, value: "HIGH" },
558
+ [EFFORT.HIGHEST]: { papered: true, value: "HIGH" },
559
+ };
560
+ function toGeminiThinkingLevel(effort) {
561
+ return GEMINI_THINKING_LEVEL[effort];
562
+ }
563
+ // Gemini 2.5 `thinkingConfig.thinkingBudget` — every tier is a distinct token
564
+ // budget, so nothing is papered over. Floor (512) clears every 2.5 minimum;
565
+ // ceiling (24,576) is valid across 2.5 Pro (max 32,768) and Flash (max 24,576).
566
+ const GEMINI_THINKING_BUDGET = {
567
+ [EFFORT.LOWEST]: 512,
568
+ [EFFORT.LOW]: 4096,
569
+ [EFFORT.MEDIUM]: 8192,
570
+ [EFFORT.HIGH]: 16384,
571
+ [EFFORT.HIGHEST]: 24576,
572
+ };
573
+ function toGeminiThinkingBudget(effort) {
574
+ return { papered: false, value: GEMINI_THINKING_BUDGET[effort] };
575
+ }
576
+ // OpenRouter `reasoning.effort` — accepts the full minimal..xhigh ladder and
577
+ // maps to the routed provider's nearest supported level itself, so nothing is
578
+ // papered here.
579
+ const OPENROUTER_EFFORT = {
580
+ [EFFORT.LOWEST]: "minimal",
581
+ [EFFORT.LOW]: "low",
582
+ [EFFORT.MEDIUM]: "medium",
583
+ [EFFORT.HIGH]: "high",
584
+ [EFFORT.HIGHEST]: "xhigh",
585
+ };
586
+ function toOpenRouterEffort(effort) {
587
+ return { papered: false, value: OPENROUTER_EFFORT[effort] };
588
+ }
589
+
464
590
  // Enums
465
591
  var LlmMessageRole;
466
592
  (function (LlmMessageRole) {
@@ -1394,8 +1520,14 @@ var ErrorCategory;
1394
1520
  (function (ErrorCategory) {
1395
1521
  /** Error is transient and can be retried */
1396
1522
  ErrorCategory["Retryable"] = "retryable";
1397
- /** Error is due to rate limiting */
1523
+ /** Error is due to short-term rate limiting (retry after a delay) */
1398
1524
  ErrorCategory["RateLimit"] = "rate_limit";
1525
+ /**
1526
+ * Provider quota is exhausted or the account cannot be billed
1527
+ * (insufficient funds, daily quota, plan limit). Terminal and actionable —
1528
+ * retrying within the request budget will not help.
1529
+ */
1530
+ ErrorCategory["Quota"] = "quota";
1399
1531
  /** Error cannot be recovered from */
1400
1532
  ErrorCategory["Unrecoverable"] = "unrecoverable";
1401
1533
  /** Error type is unknown */
@@ -1486,11 +1618,120 @@ function isTransientNetworkError(error) {
1486
1618
  return false;
1487
1619
  }
1488
1620
 
1621
+ //
1622
+ //
1623
+ // Constants
1624
+ //
1625
+ /**
1626
+ * Transient structured-output compile failures. The provider caches the
1627
+ * compiled grammar after a successful compile, so an immediate retry of the
1628
+ * identical request typically succeeds (issue #422).
1629
+ */
1630
+ const RETRYABLE_MESSAGE_PATTERNS = [
1631
+ "grammar compilation timed out",
1632
+ "grammar compilation timeout",
1633
+ ];
1634
+ /** Billing / insufficient-funds signals — the account cannot be charged. */
1635
+ const BILLING_MESSAGE_PATTERNS = [
1636
+ "insufficient_quota",
1637
+ "insufficient funds",
1638
+ "insufficient credit",
1639
+ "credit balance",
1640
+ "billing",
1641
+ "payment required",
1642
+ "plan and billing",
1643
+ ];
1644
+ /** Exhausted usage quota (per-day / per-model limits). */
1645
+ const QUOTA_MESSAGE_PATTERNS = [
1646
+ "quota exceeded",
1647
+ "exceeded your current quota",
1648
+ "resource_exhausted",
1649
+ "quota_exceeded",
1650
+ ];
1651
+ //
1652
+ //
1653
+ // Helpers
1654
+ //
1655
+ function extractMessage(error) {
1656
+ if (error instanceof Error)
1657
+ return error.message.toLowerCase();
1658
+ if (typeof error === "string")
1659
+ return error.toLowerCase();
1660
+ if (error && typeof error === "object") {
1661
+ const message = error.message;
1662
+ if (typeof message === "string")
1663
+ return message.toLowerCase();
1664
+ }
1665
+ return "";
1666
+ }
1667
+ //
1668
+ //
1669
+ // Main
1670
+ //
1671
+ /**
1672
+ * Shared, provider-agnostic first pass over an error. Returns a
1673
+ * {@link ClassifiedError} when the message unambiguously identifies a
1674
+ * cross-provider condition (retryable structured-output timeout, exhausted
1675
+ * quota, or a billing failure), or `undefined` to defer to the adapter's own
1676
+ * status/name classification.
1677
+ *
1678
+ * Adapters call this before their existing logic so that, for example, a
1679
+ * `429` carrying a daily-quota message is classified as {@link
1680
+ * ErrorCategory.Quota} rather than {@link ErrorCategory.RateLimit}.
1681
+ */
1682
+ function classifyProviderError(error) {
1683
+ const message = extractMessage(error);
1684
+ if (!message)
1685
+ return undefined;
1686
+ for (const pattern of RETRYABLE_MESSAGE_PATTERNS) {
1687
+ if (message.includes(pattern)) {
1688
+ return { category: ErrorCategory.Retryable, error, shouldRetry: true };
1689
+ }
1690
+ }
1691
+ for (const pattern of BILLING_MESSAGE_PATTERNS) {
1692
+ if (message.includes(pattern)) {
1693
+ return {
1694
+ category: ErrorCategory.Quota,
1695
+ error,
1696
+ reason: "billing",
1697
+ shouldRetry: false,
1698
+ };
1699
+ }
1700
+ }
1701
+ for (const pattern of QUOTA_MESSAGE_PATTERNS) {
1702
+ if (message.includes(pattern)) {
1703
+ return {
1704
+ category: ErrorCategory.Quota,
1705
+ error,
1706
+ reason: "quota",
1707
+ shouldRetry: false,
1708
+ };
1709
+ }
1710
+ }
1711
+ return undefined;
1712
+ }
1713
+
1489
1714
  //
1490
1715
  //
1491
1716
  // Constants
1492
1717
  //
1493
1718
  const STRUCTURED_OUTPUT_TOOL_NAME$3 = "structured_output";
1719
+ /**
1720
+ * Whether `output_config.effort` may be sent for this model. Anthropic added
1721
+ * the effort control on the Claude 4.5 line and up (the 5 family); older models
1722
+ * reject it. Parsing major/minor avoids matching a minor "5" in legacy ids
1723
+ * like `claude-3-5-sonnet`.
1724
+ */
1725
+ function supportsAnthropicEffort(model) {
1726
+ const match = model.match(/claude-[a-z]+-(\d+)(?:-(\d+))?/);
1727
+ if (!match)
1728
+ return false;
1729
+ const major = Number(match[1]);
1730
+ const minor = match[2] !== undefined ? Number(match[2]) : 0;
1731
+ if (major >= 5)
1732
+ return true;
1733
+ return major === 4 && minor >= 5;
1734
+ }
1494
1735
  const STRUCTURED_OUTPUT_NON_PARSE_STOP_REASONS = new Set([
1495
1736
  "refusal",
1496
1737
  "max_tokens",
@@ -1756,7 +1997,7 @@ class AnthropicAdapter extends BaseProviderAdapter {
1756
1997
  constructor() {
1757
1998
  super(...arguments);
1758
1999
  this.name = PROVIDER.ANTHROPIC.NAME;
1759
- this.defaultModel = PROVIDER.ANTHROPIC.MODEL.DEFAULT;
2000
+ this.defaultModel = PROVIDER.ANTHROPIC.DEFAULT;
1760
2001
  // Session-level cache of models observed to reject `temperature` at runtime.
1761
2002
  // Populated by executeRequest on 400 errors so repeat calls skip the param.
1762
2003
  this.runtimeNoTemperatureModels = new Set();
@@ -1896,6 +2137,24 @@ class AnthropicAdapter extends BaseProviderAdapter {
1896
2137
  if (request.providerOptions) {
1897
2138
  Object.assign(anthropicRequest, request.providerOptions);
1898
2139
  }
2140
+ // Normalized reasoning effort -> output_config.effort (merged so a format
2141
+ // config above survives). First-class effort wins over providerOptions.
2142
+ if (request.effort &&
2143
+ supportsAnthropicEffort(anthropicRequest.model)) {
2144
+ const mapping = toAnthropicEffort(request.effort);
2145
+ if (mapping.papered) {
2146
+ log$2.debug(paperedEffortMessage({
2147
+ model: anthropicRequest.model,
2148
+ provider: this.name,
2149
+ requested: request.effort,
2150
+ value: mapping.value,
2151
+ }));
2152
+ }
2153
+ anthropicRequest.output_config = {
2154
+ ...anthropicRequest.output_config,
2155
+ effort: mapping.value,
2156
+ };
2157
+ }
1899
2158
  // First-class temperature takes precedence over providerOptions
1900
2159
  if (request.temperature !== undefined) {
1901
2160
  anthropicRequest.temperature = request.temperature;
@@ -1990,9 +2249,13 @@ class AnthropicAdapter extends BaseProviderAdapter {
1990
2249
  */
1991
2250
  toFallbackStructuredOutputRequest(request) {
1992
2251
  const { output_config, ...rest } = request;
1993
- if (!output_config)
2252
+ if (!output_config?.format)
1994
2253
  return request;
1995
2254
  const fallbackRequest = { ...rest };
2255
+ // Preserve an effort setting; only the structured-output format falls back.
2256
+ if (output_config.effort) {
2257
+ fallbackRequest.output_config = { effort: output_config.effort };
2258
+ }
1996
2259
  const fakeTool = {
1997
2260
  name: STRUCTURED_OUTPUT_TOOL_NAME$3,
1998
2261
  description: "Output a structured JSON object, " +
@@ -2221,6 +2484,11 @@ class AnthropicAdapter extends BaseProviderAdapter {
2221
2484
  // Error Classification
2222
2485
  //
2223
2486
  classifyError(error) {
2487
+ // Shared first pass: retryable structured-output timeouts (#422),
2488
+ // quota exhaustion, and billing failures classify the same across providers.
2489
+ const shared = classifyProviderError(error);
2490
+ if (shared)
2491
+ return shared;
2224
2492
  const errorName = error?.constructor?.name;
2225
2493
  // Check for rate limit error
2226
2494
  if (errorName === "RateLimitError") {
@@ -2436,7 +2704,7 @@ class BedrockAdapter extends BaseProviderAdapter {
2436
2704
  constructor() {
2437
2705
  super(...arguments);
2438
2706
  this.name = PROVIDER.BEDROCK.NAME;
2439
- this.defaultModel = PROVIDER.BEDROCK.MODEL.DEFAULT;
2707
+ this.defaultModel = PROVIDER.BEDROCK.DEFAULT;
2440
2708
  this._modelsFallbackToStructuredOutputTool = new Set();
2441
2709
  this._modelsWithoutTemperature = new Set();
2442
2710
  }
@@ -2829,6 +3097,11 @@ class BedrockAdapter extends BaseProviderAdapter {
2829
3097
  // Error Classification
2830
3098
  //
2831
3099
  classifyError(error) {
3100
+ // Shared first pass: retryable structured-output timeouts (#422),
3101
+ // quota exhaustion, and billing failures classify the same across providers.
3102
+ const shared = classifyProviderError(error);
3103
+ if (shared)
3104
+ return shared;
2832
3105
  const errorName = error?.constructor?.name;
2833
3106
  const errorMessage = error?.message ?? "";
2834
3107
  if (errorName === "ThrottlingException" ||
@@ -2942,6 +3215,11 @@ const STRUCTURED_OUTPUT_TOOL_NAME$1 = "structured_output";
2942
3215
  // (including 2.5 thinking) do not support the combo and fall back to the
2943
3216
  // legacy `structured_output` fake-tool emulation.
2944
3217
  const GEMINI_3_PATTERN = /^gemini-3/;
3218
+ // Reasoning-effort control differs by generation: Gemini 3.x takes the
3219
+ // `thinkingLevel` enum, Gemini 2.5 takes an integer `thinkingBudget`. Sending
3220
+ // the wrong one errors, and models outside these families have no thinking
3221
+ // control, so effort is only applied when one of these matches.
3222
+ const GEMINI_25_PATTERN = /^gemini-2\.5/;
2945
3223
  /**
2946
3224
  * Detect 4xx errors that indicate the model itself does not support the
2947
3225
  * `responseJsonSchema` + tools combo. Triggers the runtime fallback to the
@@ -2988,7 +3266,7 @@ class GoogleAdapter extends BaseProviderAdapter {
2988
3266
  constructor() {
2989
3267
  super(...arguments);
2990
3268
  this.name = PROVIDER.GOOGLE.NAME;
2991
- this.defaultModel = PROVIDER.GOOGLE.MODEL.DEFAULT;
3269
+ this.defaultModel = PROVIDER.GOOGLE.DEFAULT;
2992
3270
  // Session-level cache of Gemini 3 models observed to reject the native
2993
3271
  // `responseJsonSchema` + tools combo. When a model is in this set,
2994
3272
  // buildRequest engages the legacy fake-tool path instead.
@@ -3116,6 +3394,42 @@ class GoogleAdapter extends BaseProviderAdapter {
3116
3394
  ...request.providerOptions,
3117
3395
  };
3118
3396
  }
3397
+ // Normalized reasoning effort -> thinkingConfig. Gemini 3.x uses the
3398
+ // thinkingLevel enum; 2.5 uses an integer thinkingBudget. Merged so a
3399
+ // providerOptions.thinkingConfig survives; first-class effort wins.
3400
+ if (request.effort) {
3401
+ const model = geminiRequest.model;
3402
+ let thinkingConfig;
3403
+ let papered = false;
3404
+ let value;
3405
+ if (GEMINI_3_PATTERN.test(model)) {
3406
+ const mapping = toGeminiThinkingLevel(request.effort);
3407
+ ({ papered, value } = mapping);
3408
+ thinkingConfig = { thinkingLevel: mapping.value };
3409
+ }
3410
+ else if (GEMINI_25_PATTERN.test(model)) {
3411
+ const mapping = toGeminiThinkingBudget(request.effort);
3412
+ ({ papered, value } = mapping);
3413
+ thinkingConfig = { thinkingBudget: mapping.value };
3414
+ }
3415
+ if (thinkingConfig) {
3416
+ if (papered) {
3417
+ log$2.debug(paperedEffortMessage({
3418
+ model,
3419
+ provider: this.name,
3420
+ requested: request.effort,
3421
+ value: value,
3422
+ }));
3423
+ }
3424
+ geminiRequest.config = {
3425
+ ...geminiRequest.config,
3426
+ thinkingConfig: {
3427
+ ...geminiRequest.config?.thinkingConfig,
3428
+ ...thinkingConfig,
3429
+ },
3430
+ };
3431
+ }
3432
+ }
3119
3433
  // First-class temperature takes precedence over providerOptions
3120
3434
  if (request.temperature !== undefined) {
3121
3435
  geminiRequest.config = {
@@ -3457,6 +3771,11 @@ class GoogleAdapter extends BaseProviderAdapter {
3457
3771
  // Error Classification
3458
3772
  //
3459
3773
  classifyError(error) {
3774
+ // Shared first pass: retryable structured-output timeouts (#422),
3775
+ // quota exhaustion, and billing failures classify the same across providers.
3776
+ const shared = classifyProviderError(error);
3777
+ if (shared)
3778
+ return shared;
3460
3779
  const geminiError = error;
3461
3780
  // Extract status code from error
3462
3781
  const statusCode = geminiError.status || geminiError.code;
@@ -4035,7 +4354,7 @@ class OpenAiAdapter extends BaseProviderAdapter {
4035
4354
  constructor() {
4036
4355
  super(...arguments);
4037
4356
  this.name = PROVIDER.OPENAI.NAME;
4038
- this.defaultModel = PROVIDER.OPENAI.MODEL.DEFAULT;
4357
+ this.defaultModel = PROVIDER.OPENAI.DEFAULT;
4039
4358
  // Session-level cache of models observed to reject `temperature` at runtime.
4040
4359
  // Populated by executeRequest on 400 errors so repeat calls skip the param.
4041
4360
  this.runtimeNoTemperatureModels = new Set();
@@ -4051,6 +4370,14 @@ class OpenAiAdapter extends BaseProviderAdapter {
4051
4370
  return false;
4052
4371
  return !MODELS_WITHOUT_TEMPERATURE$1.some((pattern) => pattern.test(model));
4053
4372
  }
4373
+ /** Whether `reasoning.effort` may be sent for this model. Overridden by xAI. */
4374
+ supportsReasoningEffort(model) {
4375
+ return isReasoningModel(model);
4376
+ }
4377
+ /** Translate a normalized effort to this provider's `reasoning.effort` value. */
4378
+ mapReasoningEffort(effort, model) {
4379
+ return toOpenAiEffort(effort, { model });
4380
+ }
4054
4381
  //
4055
4382
  // Request Building
4056
4383
  //
@@ -4089,6 +4416,24 @@ class OpenAiAdapter extends BaseProviderAdapter {
4089
4416
  if (request.providerOptions) {
4090
4417
  Object.assign(openaiRequest, request.providerOptions);
4091
4418
  }
4419
+ // Normalized reasoning effort -> reasoning.effort (merged so the
4420
+ // summary:auto above survives). First-class effort wins over providerOptions.
4421
+ if (request.effort && this.supportsReasoningEffort(model)) {
4422
+ const mapping = this.mapReasoningEffort(request.effort, model);
4423
+ if (mapping.papered) {
4424
+ log$2.debug(paperedEffortMessage({
4425
+ model,
4426
+ provider: this.name,
4427
+ requested: request.effort,
4428
+ value: mapping.value,
4429
+ }));
4430
+ }
4431
+ const existingReasoning = openaiRequest.reasoning ?? {};
4432
+ openaiRequest.reasoning = {
4433
+ ...existingReasoning,
4434
+ effort: mapping.value,
4435
+ };
4436
+ }
4092
4437
  // First-class temperature takes precedence over providerOptions
4093
4438
  if (request.temperature !== undefined) {
4094
4439
  openaiRequest.temperature = request.temperature;
@@ -4379,6 +4724,11 @@ class OpenAiAdapter extends BaseProviderAdapter {
4379
4724
  // Error Classification
4380
4725
  //
4381
4726
  classifyError(error) {
4727
+ // Shared first pass: retryable structured-output timeouts (#422),
4728
+ // quota exhaustion, and billing failures classify the same across providers.
4729
+ const shared = classifyProviderError(error);
4730
+ if (shared)
4731
+ return shared;
4382
4732
  // Check for rate limit error
4383
4733
  if (error instanceof RateLimitError$1) {
4384
4734
  return {
@@ -4683,7 +5033,7 @@ class OpenRouterAdapter extends BaseProviderAdapter {
4683
5033
  constructor() {
4684
5034
  super(...arguments);
4685
5035
  this.name = PROVIDER.OPENROUTER.NAME;
4686
- this.defaultModel = PROVIDER.OPENROUTER.MODEL.DEFAULT;
5036
+ this.defaultModel = PROVIDER.OPENROUTER.DEFAULT;
4687
5037
  // Session-level cache of models observed to reject native
4688
5038
  // `response_format: json_schema`. When a model is in this set, buildRequest
4689
5039
  // engages the legacy fake-tool path instead of native structured output.
@@ -4776,6 +5126,15 @@ class OpenRouterAdapter extends BaseProviderAdapter {
4776
5126
  if (request.providerOptions) {
4777
5127
  Object.assign(openRouterRequest, request.providerOptions);
4778
5128
  }
5129
+ // Normalized reasoning effort -> reasoning.effort. OpenRouter accepts the
5130
+ // full ladder and maps to the routed provider's nearest supported level.
5131
+ // First-class effort wins over providerOptions.
5132
+ if (request.effort) {
5133
+ openRouterRequest.reasoning = {
5134
+ ...openRouterRequest.reasoning,
5135
+ effort: toOpenRouterEffort(request.effort).value,
5136
+ };
5137
+ }
4779
5138
  // First-class temperature takes precedence over providerOptions
4780
5139
  if (request.temperature !== undefined) {
4781
5140
  openRouterRequest.temperature =
@@ -5138,6 +5497,11 @@ class OpenRouterAdapter extends BaseProviderAdapter {
5138
5497
  // Error Classification
5139
5498
  //
5140
5499
  classifyError(error) {
5500
+ // Shared first pass: retryable structured-output timeouts (#422),
5501
+ // quota exhaustion, and billing failures classify the same across providers.
5502
+ const shared = classifyProviderError(error);
5503
+ if (shared)
5504
+ return shared;
5141
5505
  // Check if error has a status code (HTTP error)
5142
5506
  const errorWithStatus = error;
5143
5507
  const statusCode = errorWithStatus.status || errorWithStatus.statusCode;
@@ -5394,8 +5758,21 @@ class XaiAdapter extends OpenAiAdapter {
5394
5758
  super(...arguments);
5395
5759
  // @ts-expect-error Narrowing override: xAI name differs from parent's literal "openai"
5396
5760
  this.name = PROVIDER.XAI.NAME;
5397
- // @ts-expect-error Narrowing override: xAI default model differs from parent's literal
5398
- this.defaultModel = PROVIDER.XAI.MODEL.DEFAULT;
5761
+ this.defaultModel = PROVIDER.XAI.DEFAULT;
5762
+ }
5763
+ /**
5764
+ * Grok gates reasoning effort by model, not by the OpenAI `gpt-*`/`o*`
5765
+ * patterns. Only explicit `*-reasoning` models accept `reasoning_effort`;
5766
+ * bare grok-4 reasons implicitly and rejects it, so we stay conservative and
5767
+ * only opt in models whose name advertises reasoning.
5768
+ */
5769
+ supportsReasoningEffort(model) {
5770
+ if (/non-reasoning/.test(model))
5771
+ return false;
5772
+ return /reasoning/.test(model);
5773
+ }
5774
+ mapReasoningEffort(effort) {
5775
+ return toXaiEffort(effort);
5399
5776
  }
5400
5777
  classifyError(error) {
5401
5778
  if (isTransientIngestError(error)) {
@@ -6489,6 +6866,122 @@ function createStaleRejectionGuard() {
6489
6866
  };
6490
6867
  }
6491
6868
 
6869
+ //
6870
+ //
6871
+ // Base
6872
+ //
6873
+ /**
6874
+ * Normalized, provider-agnostic LLM error. Thrown by the operate/stream retry
6875
+ * layer when a request cannot be completed, so consumers can `catch` a stable
6876
+ * type regardless of which provider failed. Extends {@link JaypieError} so
6877
+ * `isJaypieError()` and `.status` continue to work; the original provider error
6878
+ * is preserved on `.cause`.
6879
+ */
6880
+ class LlmError extends JaypieError {
6881
+ constructor(message, category, { status = 502, title = "Bad Gateway", provider, model, retryAfterMs, cause, } = {}) {
6882
+ super(message, { status, title }, { _type: "LlmError" });
6883
+ this.name = "LlmError";
6884
+ this.category = category;
6885
+ this.provider = provider;
6886
+ this.model = model;
6887
+ this.retryAfterMs = retryAfterMs;
6888
+ this.cause = cause;
6889
+ }
6890
+ }
6891
+ //
6892
+ //
6893
+ // Subclasses
6894
+ //
6895
+ /**
6896
+ * Short-term rate limiting (per-minute 429). Terminal within the request
6897
+ * budget; `retryAfterMs` carries the provider's suggested wait when available.
6898
+ */
6899
+ class LlmRateLimitError extends LlmError {
6900
+ constructor(message, options = {}) {
6901
+ super(message, ErrorCategory.RateLimit, {
6902
+ status: 429,
6903
+ title: "Too Many Requests",
6904
+ ...options,
6905
+ });
6906
+ this.name = "LlmRateLimitError";
6907
+ }
6908
+ }
6909
+ /**
6910
+ * Provider quota is exhausted or the account cannot be billed. Terminal and
6911
+ * actionable. `reason` distinguishes an exhausted usage quota from insufficient
6912
+ * funds.
6913
+ */
6914
+ class LlmQuotaError extends LlmError {
6915
+ constructor(message, { reason = "quota", ...options } = {}) {
6916
+ super(message, ErrorCategory.Quota, {
6917
+ status: 402,
6918
+ title: "Payment Required",
6919
+ ...options,
6920
+ });
6921
+ this.name = "LlmQuotaError";
6922
+ this.reason = reason;
6923
+ }
6924
+ }
6925
+ /**
6926
+ * The request cannot be recovered from (bad request, authentication,
6927
+ * permission, not found). Terminal.
6928
+ */
6929
+ class LlmUnrecoverableError extends LlmError {
6930
+ constructor(message, options = {}) {
6931
+ super(message, ErrorCategory.Unrecoverable, {
6932
+ status: 502,
6933
+ title: "Bad Gateway",
6934
+ ...options,
6935
+ });
6936
+ this.name = "LlmUnrecoverableError";
6937
+ }
6938
+ }
6939
+ /**
6940
+ * A transient or unknown error survived the retry budget. Terminal only because
6941
+ * retries were exhausted; the underlying condition may succeed later.
6942
+ */
6943
+ class LlmTransientError extends LlmError {
6944
+ constructor(message, options = {}) {
6945
+ super(message, ErrorCategory.Retryable, {
6946
+ status: 504,
6947
+ title: "Gateway Timeout",
6948
+ ...options,
6949
+ });
6950
+ this.name = "LlmTransientError";
6951
+ }
6952
+ }
6953
+
6954
+ /**
6955
+ * Map a {@link ClassifiedError} to the matching {@link LlmError} subclass,
6956
+ * preserving the original error as `cause`. Used by the retry layer to throw a
6957
+ * stable, catchable type instead of a bare gateway error.
6958
+ */
6959
+ function toLlmError(classified, context = {}) {
6960
+ const original = classified.error;
6961
+ const message = original instanceof Error ? original.message : String(original);
6962
+ const options = {
6963
+ cause: original,
6964
+ model: context.model,
6965
+ provider: context.provider,
6966
+ retryAfterMs: classified.suggestedDelayMs,
6967
+ };
6968
+ switch (classified.category) {
6969
+ case ErrorCategory.RateLimit:
6970
+ return new LlmRateLimitError(message, options);
6971
+ case ErrorCategory.Quota:
6972
+ return new LlmQuotaError(message, {
6973
+ ...options,
6974
+ reason: classified.reason ?? "quota",
6975
+ });
6976
+ case ErrorCategory.Unrecoverable:
6977
+ return new LlmUnrecoverableError(message, options);
6978
+ case ErrorCategory.Retryable:
6979
+ case ErrorCategory.Unknown:
6980
+ default:
6981
+ return new LlmTransientError(message, options);
6982
+ }
6983
+ }
6984
+
6492
6985
  //
6493
6986
  //
6494
6987
  // Main
@@ -6503,6 +6996,18 @@ class RetryExecutor {
6503
6996
  this.hookRunner = config.hookRunner ?? hookRunner;
6504
6997
  this.errorClassifier = config.errorClassifier;
6505
6998
  }
6999
+ /**
7000
+ * Build the typed, provider-agnostic error thrown when a request cannot be
7001
+ * completed — classified (rate limit / quota / unrecoverable / transient)
7002
+ * and carrying the provider, model, and original error as `cause`.
7003
+ */
7004
+ toTerminalError(error, context) {
7005
+ const classified = this.errorClassifier.classify(error);
7006
+ return toLlmError(classified, {
7007
+ model: context.model,
7008
+ provider: context.provider,
7009
+ });
7010
+ }
6506
7011
  /**
6507
7012
  * Execute an operation with retry logic.
6508
7013
  * Each attempt receives an AbortSignal. On failure, the signal is aborted
@@ -6546,8 +7051,7 @@ class RetryExecutor {
6546
7051
  providerRequest: options.context.providerRequest,
6547
7052
  error,
6548
7053
  });
6549
- const errorMessage = error instanceof Error ? error.message : String(error);
6550
- throw new BadGatewayError(errorMessage);
7054
+ throw this.toTerminalError(error, options.context);
6551
7055
  }
6552
7056
  // Check if error is not retryable
6553
7057
  if (!this.errorClassifier.isRetryable(error)) {
@@ -6559,8 +7063,7 @@ class RetryExecutor {
6559
7063
  providerRequest: options.context.providerRequest,
6560
7064
  error,
6561
7065
  });
6562
- const errorMessage = error instanceof Error ? error.message : String(error);
6563
- throw new BadGatewayError(errorMessage);
7066
+ throw this.toTerminalError(error, options.context);
6564
7067
  }
6565
7068
  // Warn if this is an unknown error type
6566
7069
  if (!this.errorClassifier.isKnownError(error)) {
@@ -6603,6 +7106,7 @@ const MAX_CONSECUTIVE_TOOL_ERRORS = 6;
6603
7106
  */
6604
7107
  function createErrorClassifier(adapter) {
6605
7108
  return {
7109
+ classify: (error) => adapter.classifyError(error),
6606
7110
  isRetryable: (error) => adapter.isRetryableError(error),
6607
7111
  isKnownError: (error) => {
6608
7112
  const classified = adapter.classifyError(error);
@@ -6672,6 +7176,7 @@ class OperateLoop {
6672
7176
  }
6673
7177
  // Rebuild request with updated history for next turn
6674
7178
  request = {
7179
+ effort: options.effort,
6675
7180
  format: state.formattedFormat,
6676
7181
  instructions: options.instructions,
6677
7182
  messages: state.currentInput,
@@ -6775,6 +7280,7 @@ class OperateLoop {
6775
7280
  }
6776
7281
  buildInitialRequest(state, options) {
6777
7282
  return {
7283
+ effort: options.effort,
6778
7284
  format: state.formattedFormat,
6779
7285
  instructions: options.instructions,
6780
7286
  messages: state.currentInput,
@@ -6854,7 +7360,9 @@ class OperateLoop {
6854
7360
  const response = await retryExecutor.execute((signal) => this.adapter.executeRequest(this.client, providerRequest, signal), {
6855
7361
  context: {
6856
7362
  input: state.currentInput,
7363
+ model: options.model ?? this.adapter.defaultModel,
6857
7364
  options,
7365
+ provider: this.adapter.name,
6858
7366
  providerRequest,
6859
7367
  },
6860
7368
  hooks: hooksWithProgress,
@@ -7289,6 +7797,7 @@ class StreamLoop {
7289
7797
  }
7290
7798
  // Rebuild request with updated history for next turn
7291
7799
  request = {
7800
+ effort: options.effort,
7292
7801
  format: state.formattedFormat,
7293
7802
  instructions: options.instructions,
7294
7803
  messages: state.currentInput,
@@ -7364,6 +7873,7 @@ class StreamLoop {
7364
7873
  }
7365
7874
  buildInitialRequest(state, options) {
7366
7875
  return {
7876
+ effort: options.effort,
7367
7877
  format: state.formattedFormat,
7368
7878
  instructions: options.instructions,
7369
7879
  messages: state.currentInput,
@@ -7475,9 +7985,11 @@ class StreamLoop {
7475
7985
  !this.adapter.isRetryableError(error)) {
7476
7986
  log.error(`Stream request failed after ${this.retryPolicy.maxRetries} retries`);
7477
7987
  log.var({ error });
7478
- const errorMessage = error instanceof Error ? error.message : String(error);
7479
7988
  llmSpan?.finish();
7480
- throw new BadGatewayError(errorMessage);
7989
+ throw toLlmError(this.adapter.classifyError(error), {
7990
+ model: options.model ?? this.adapter.defaultModel,
7991
+ provider: this.adapter.name,
7992
+ });
7481
7993
  }
7482
7994
  const delay = this.retryPolicy.getDelayForAttempt(attempt);
7483
7995
  log.warn(`Stream request failed. Retrying in ${delay}ms...`);
@@ -7927,7 +8439,7 @@ async function createStructuredCompletion$1(client, messages, model, responseSch
7927
8439
 
7928
8440
  // Main class implementation
7929
8441
  class AnthropicProvider {
7930
- constructor(model = PROVIDER.ANTHROPIC.MODEL.DEFAULT, { apiKey } = {}) {
8442
+ constructor(model = PROVIDER.ANTHROPIC.DEFAULT, { apiKey } = {}) {
7931
8443
  this.log = getLogger$5();
7932
8444
  this.conversationHistory = [];
7933
8445
  this.model = model;
@@ -8035,7 +8547,7 @@ async function initializeClient$4({ region, } = {}) {
8035
8547
  }
8036
8548
 
8037
8549
  class BedrockProvider {
8038
- constructor(model = PROVIDER.BEDROCK.MODEL.DEFAULT, { region } = {}) {
8550
+ constructor(model = PROVIDER.BEDROCK.DEFAULT, { region } = {}) {
8039
8551
  this.log = getLogger$4();
8040
8552
  this.conversationHistory = [];
8041
8553
  this.model = model;
@@ -8278,7 +8790,7 @@ function prepareMessages$2(message, { data, placeholders } = {}) {
8278
8790
  }
8279
8791
 
8280
8792
  class GoogleProvider {
8281
- constructor(model = PROVIDER.GOOGLE.MODEL.DEFAULT, { apiKey } = {}) {
8793
+ constructor(model = PROVIDER.GOOGLE.DEFAULT, { apiKey } = {}) {
8282
8794
  this.log = getLogger$3();
8283
8795
  this.conversationHistory = [];
8284
8796
  this.model = model;
@@ -8480,7 +8992,7 @@ async function createTextCompletion(client, { messages, model, }) {
8480
8992
  }
8481
8993
 
8482
8994
  class OpenAiProvider {
8483
- constructor(model = PROVIDER.OPENAI.MODEL.DEFAULT, { apiKey } = {}) {
8995
+ constructor(model = PROVIDER.OPENAI.DEFAULT, { apiKey } = {}) {
8484
8996
  this.log = getLogger$2();
8485
8997
  this.conversationHistory = [];
8486
8998
  this.model = model;
@@ -8711,7 +9223,7 @@ async function initializeClient$1({ apiKey, } = {}) {
8711
9223
  }
8712
9224
  // Get default model from environment or constants
8713
9225
  function getDefaultModel() {
8714
- return process.env.OPENROUTER_MODEL || PROVIDER.OPENROUTER.MODEL.DEFAULT;
9226
+ return process.env.OPENROUTER_MODEL || PROVIDER.OPENROUTER.DEFAULT;
8715
9227
  }
8716
9228
  function formatSystemMessage(systemPrompt, { data, placeholders: placeholders$1 } = {}) {
8717
9229
  const content = placeholders$1?.system === false
@@ -8863,7 +9375,7 @@ async function initializeClient({ apiKey, } = {}) {
8863
9375
  }
8864
9376
 
8865
9377
  class XaiProvider {
8866
- constructor(model = PROVIDER.XAI.MODEL.DEFAULT, { apiKey } = {}) {
9378
+ constructor(model = PROVIDER.XAI.DEFAULT, { apiKey } = {}) {
8867
9379
  this.log = getLogger$2();
8868
9380
  this.conversationHistory = [];
8869
9381
  this.model = model;
@@ -8948,7 +9460,14 @@ class XaiProvider {
8948
9460
 
8949
9461
  class Llm {
8950
9462
  constructor(providerName = DEFAULT.PROVIDER.NAME, options = {}) {
8951
- const { fallback, model } = options;
9463
+ const { fallback: fallbackOption, model: modelOption } = options;
9464
+ // A `model` array is sugar for a preference-ordered fallback chain:
9465
+ // index 0 is primary, the rest become fallback entries (provider
9466
+ // auto-detected). Any explicit `fallback` is appended after the chain.
9467
+ const { fallback: modelFallback, model } = resolveModelChain(modelOption);
9468
+ const fallback = modelFallback.length || fallbackOption
9469
+ ? [...modelFallback, ...(fallbackOption ?? [])]
9470
+ : undefined;
8952
9471
  let finalProvider = providerName;
8953
9472
  let finalModel = model;
8954
9473
  // Legacy: accept "gemini" but warn
@@ -8994,23 +9513,25 @@ class Llm {
8994
9513
  const { apiKey, model } = options;
8995
9514
  switch (providerName) {
8996
9515
  case PROVIDER.ANTHROPIC.NAME:
8997
- return new AnthropicProvider(model || PROVIDER.ANTHROPIC.MODEL.DEFAULT, { apiKey });
9516
+ return new AnthropicProvider(model || PROVIDER.ANTHROPIC.DEFAULT, {
9517
+ apiKey,
9518
+ });
8998
9519
  case PROVIDER.BEDROCK.NAME:
8999
- return new BedrockProvider(model || PROVIDER.BEDROCK.MODEL.DEFAULT);
9520
+ return new BedrockProvider(model || PROVIDER.BEDROCK.DEFAULT);
9000
9521
  case PROVIDER.GOOGLE.NAME:
9001
- return new GoogleProvider(model || PROVIDER.GOOGLE.MODEL.DEFAULT, {
9522
+ return new GoogleProvider(model || PROVIDER.GOOGLE.DEFAULT, {
9002
9523
  apiKey,
9003
9524
  });
9004
9525
  case PROVIDER.OPENAI.NAME:
9005
- return new OpenAiProvider(model || PROVIDER.OPENAI.MODEL.DEFAULT, {
9526
+ return new OpenAiProvider(model || PROVIDER.OPENAI.DEFAULT, {
9006
9527
  apiKey,
9007
9528
  });
9008
9529
  case PROVIDER.OPENROUTER.NAME:
9009
- return new OpenRouterProvider(model || PROVIDER.OPENROUTER.MODEL.DEFAULT, {
9530
+ return new OpenRouterProvider(model || PROVIDER.OPENROUTER.DEFAULT, {
9010
9531
  apiKey,
9011
9532
  });
9012
9533
  case PROVIDER.XAI.NAME:
9013
- return new XaiProvider(model || PROVIDER.XAI.MODEL.DEFAULT, {
9534
+ return new XaiProvider(model || PROVIDER.XAI.DEFAULT, {
9014
9535
  apiKey,
9015
9536
  });
9016
9537
  default:
@@ -9018,6 +9539,11 @@ class Llm {
9018
9539
  }
9019
9540
  }
9020
9541
  async send(message, options) {
9542
+ if (options && Array.isArray(options.model)) {
9543
+ // `send` has no fallback machinery; use the primary model only
9544
+ const { model } = resolveModelChain(options.model);
9545
+ return this._llm.send(message, { ...options, model });
9546
+ }
9021
9547
  return this._llm.send(message, options);
9022
9548
  }
9023
9549
  /**
@@ -9050,8 +9576,21 @@ class Llm {
9050
9576
  if (!this._llm.operate) {
9051
9577
  throw new NotImplementedError(`Provider ${this._provider} does not support operate method`);
9052
9578
  }
9053
- const fallbackChain = this.resolveFallbackChain(options);
9054
- const optionsWithoutFallback = { ...options, fallback: false };
9579
+ // A per-call `model` array becomes primary + a derived fallback chain
9580
+ // prepended to any resolved chain.
9581
+ const { fallback: modelFallback, model: perCallModel } = resolveModelChain(options.model);
9582
+ const resolvedOptions = {
9583
+ ...options,
9584
+ model: perCallModel,
9585
+ };
9586
+ const fallbackChain = [
9587
+ ...modelFallback,
9588
+ ...this.resolveFallbackChain(resolvedOptions),
9589
+ ];
9590
+ const optionsWithoutFallback = {
9591
+ ...resolvedOptions,
9592
+ fallback: false,
9593
+ };
9055
9594
  let lastError;
9056
9595
  let attempts = 0;
9057
9596
  // Try primary provider first
@@ -9100,33 +9639,45 @@ class Llm {
9100
9639
  if (!this._llm.stream) {
9101
9640
  throw new NotImplementedError(`Provider ${this._provider} does not support stream method`);
9102
9641
  }
9103
- yield* this._llm.stream(input, options);
9642
+ // `stream` has no instance-level fallback machinery; an array model uses
9643
+ // the primary and ignores the rest.
9644
+ const { model } = resolveModelChain(options.model);
9645
+ const streamOptions = { ...options, model };
9646
+ yield* this._llm.stream(input, streamOptions);
9104
9647
  }
9105
9648
  static async send(message, options) {
9106
9649
  const { llm, apiKey, model, ...messageOptions } = options || {};
9107
- const instance = new Llm(llm, { apiKey, model });
9650
+ // A `model` array derives a fallback chain; `send` has no fallback
9651
+ // machinery, so the primary model is used and the chain is ignored.
9652
+ const { model: primaryModel } = resolveModelChain(model);
9653
+ const instance = new Llm(llm, { apiKey, model: primaryModel });
9108
9654
  return instance.send(message, messageOptions);
9109
9655
  }
9110
9656
  static async operate(input, options) {
9111
9657
  const { apiKey, fallback, llm, model, ...operateOptions } = options || {};
9658
+ // A `model` array becomes primary + derived fallback chain
9659
+ const { fallback: modelFallback, model: primaryModel } = resolveModelChain(model);
9112
9660
  let finalLlm = llm;
9113
- let finalModel = model;
9114
- if (!llm && model) {
9115
- const determined = determineModelProvider(model);
9661
+ let finalModel = primaryModel;
9662
+ if (!llm && primaryModel) {
9663
+ const determined = determineModelProvider(primaryModel);
9116
9664
  if (determined.provider) {
9117
9665
  finalLlm = determined.provider;
9118
9666
  }
9119
9667
  }
9120
- else if (llm && model) {
9668
+ else if (llm && primaryModel) {
9121
9669
  // When both llm and model are provided, check if they conflict
9122
- const determined = determineModelProvider(model);
9670
+ const determined = determineModelProvider(primaryModel);
9123
9671
  if (determined.provider && determined.provider !== llm) {
9124
9672
  // Don't pass the conflicting model to the constructor
9125
9673
  finalModel = undefined;
9126
9674
  }
9127
9675
  }
9128
9676
  // Resolve fallback for static method: pass to instance if array, pass to operate options if false
9129
- const instanceFallback = Array.isArray(fallback) ? fallback : undefined;
9677
+ const explicitFallback = Array.isArray(fallback) ? fallback : [];
9678
+ const instanceFallback = modelFallback.length || explicitFallback.length
9679
+ ? [...modelFallback, ...explicitFallback]
9680
+ : undefined;
9130
9681
  const operateFallback = fallback === false ? false : undefined;
9131
9682
  const instance = new Llm(finalLlm, {
9132
9683
  apiKey,
@@ -9140,23 +9691,29 @@ class Llm {
9140
9691
  }
9141
9692
  static stream(input, options) {
9142
9693
  const { llm, apiKey, model, ...streamOptions } = options || {};
9694
+ // A `model` array becomes primary + derived fallback chain
9695
+ const { fallback: modelFallback, model: primaryModel } = resolveModelChain(model);
9143
9696
  let finalLlm = llm;
9144
- let finalModel = model;
9145
- if (!llm && model) {
9146
- const determined = determineModelProvider(model);
9697
+ let finalModel = primaryModel;
9698
+ if (!llm && primaryModel) {
9699
+ const determined = determineModelProvider(primaryModel);
9147
9700
  if (determined.provider) {
9148
9701
  finalLlm = determined.provider;
9149
9702
  }
9150
9703
  }
9151
- else if (llm && model) {
9704
+ else if (llm && primaryModel) {
9152
9705
  // When both llm and model are provided, check if they conflict
9153
- const determined = determineModelProvider(model);
9706
+ const determined = determineModelProvider(primaryModel);
9154
9707
  if (determined.provider && determined.provider !== llm) {
9155
9708
  // Don't pass the conflicting model to the constructor
9156
9709
  finalModel = undefined;
9157
9710
  }
9158
9711
  }
9159
- const instance = new Llm(finalLlm, { apiKey, model: finalModel });
9712
+ const instance = new Llm(finalLlm, {
9713
+ apiKey,
9714
+ fallback: modelFallback.length ? modelFallback : undefined,
9715
+ model: finalModel,
9716
+ });
9160
9717
  return instance.stream(input, streamOptions);
9161
9718
  }
9162
9719
  }
@@ -9454,5 +10011,5 @@ const toolkit = new JaypieToolkit(tools);
9454
10011
  [LlmMessageRole.Developer]: "user",
9455
10012
  });
9456
10013
 
9457
- export { BedrockProvider, GoogleProvider as GeminiProvider, GoogleProvider, JaypieToolkit, constants as LLM, Llm, LlmMessageRole, LlmMessageType, LlmProgressEventType, LlmStreamChunkType, OpenRouterProvider, Toolkit, XaiProvider, extractReasoning, isLlmOperateInput, isLlmOperateInputContent, isLlmOperateInputFile, isLlmOperateInputImage, jsonSchemaToNaturalSchema, naturalSchemaToJsonSchema, toolkit, tools };
10014
+ export { BedrockProvider, ErrorCategory, GoogleProvider as GeminiProvider, GoogleProvider, JaypieToolkit, constants as LLM, Llm, LlmError, LlmMessageRole, LlmMessageType, LlmProgressEventType, LlmQuotaError, LlmRateLimitError, LlmStreamChunkType, LlmTransientError, LlmUnrecoverableError, OpenRouterProvider, Toolkit, XaiProvider, extractReasoning, isLlmOperateInput, isLlmOperateInputContent, isLlmOperateInputFile, isLlmOperateInputImage, jsonSchemaToNaturalSchema, naturalSchemaToJsonSchema, toolkit, tools };
9458
10015
  //# sourceMappingURL=index.js.map