@jaypie/llm 1.3.9 → 1.3.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/esm/index.js CHANGED
@@ -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("/")) {
@@ -461,6 +438,125 @@ class BaseProviderAdapter {
461
438
  }
462
439
  }
463
440
 
441
+ /**
442
+ * Per-provider translation of the provider-neutral {@link LlmEffort} scale.
443
+ *
444
+ * The neutral enum (lowest → low → medium → high → highest) is a relative
445
+ * five-point scale. Each table below maps it onto the target provider's native
446
+ * effort control, keeping `medium`/`high` semantically aligned across providers
447
+ * and using each provider's extra bottom (`minimal`) or top (`xhigh`/`max`)
448
+ * rung where one exists. Providers with fewer levels collapse the ends and mark
449
+ * the result `papered`. A single `effort` value is therefore safe to reuse
450
+ * across providers and fallback chains.
451
+ */
452
+ /** Consistent debug message for a papered-over effort level. */
453
+ function paperedEffortMessage({ model, provider, requested, value, }) {
454
+ return `[llm] effort '${requested}' has no distinct tier on ${provider} model '${model}'; using '${value}'`;
455
+ }
456
+ // OpenAI Responses API `reasoning.effort`.
457
+ // Full ladder: minimal | low | medium | high | xhigh (plus `none`). Availability
458
+ // is not uniform across the gpt-5 line:
459
+ // - `xhigh` was introduced at gpt-5.2 and has been continuous since, so it is
460
+ // safe for our gpt-5.4 default and everything newer.
461
+ // - `minimal` shipped on gpt-5/5.1, was dropped at gpt-5.2, and returned on
462
+ // the current line; because that history is non-monotonic we only trust it
463
+ // from gpt-5.4 (our default floor) onward.
464
+ // Outside those windows (older gpt-5, o-series) the extreme rung is clamped and
465
+ // the mapping reports `papered: true`.
466
+ const OPENAI_EFFORT = {
467
+ [EFFORT.LOWEST]: "minimal",
468
+ [EFFORT.LOW]: "low",
469
+ [EFFORT.MEDIUM]: "medium",
470
+ [EFFORT.HIGH]: "high",
471
+ [EFFORT.HIGHEST]: "xhigh",
472
+ };
473
+ function openAiGptVersion(model) {
474
+ const match = model.match(/^gpt-(\d+)(?:\.(\d+))?/);
475
+ if (!match)
476
+ return null;
477
+ return { major: Number(match[1]), minor: match[2] ? Number(match[2]) : 0 };
478
+ }
479
+ function atLeast(version, major, minor) {
480
+ if (!version)
481
+ return false;
482
+ return (version.major > major || (version.major === major && version.minor >= minor));
483
+ }
484
+ function toOpenAiEffort(effort, { model }) {
485
+ const native = OPENAI_EFFORT[effort];
486
+ const version = openAiGptVersion(model);
487
+ // `minimal` only from gpt-5.4 (non-monotonic history; absent on o-series)
488
+ if (native === "minimal" && !atLeast(version, 5, 4)) {
489
+ return { papered: true, value: "low" };
490
+ }
491
+ // `xhigh` from gpt-5.2 onward (absent on older gpt-5 and o-series)
492
+ if (native === "xhigh" && !atLeast(version, 5, 2)) {
493
+ return { papered: true, value: "high" };
494
+ }
495
+ return { papered: false, value: native };
496
+ }
497
+ // xAI Grok `reasoning_effort` — low | medium | high. No sub-low or top rung, so
498
+ // `lowest` collapses onto `low` and `highest` onto `high`.
499
+ const XAI_EFFORT = {
500
+ [EFFORT.LOWEST]: { papered: true, value: "low" },
501
+ [EFFORT.LOW]: { papered: false, value: "low" },
502
+ [EFFORT.MEDIUM]: { papered: false, value: "medium" },
503
+ [EFFORT.HIGH]: { papered: false, value: "high" },
504
+ [EFFORT.HIGHEST]: { papered: true, value: "high" },
505
+ };
506
+ function toXaiEffort(effort) {
507
+ return XAI_EFFORT[effort];
508
+ }
509
+ // Anthropic `output_config.effort` — low | medium | high | xhigh | max. No
510
+ // sub-low rung, so `lowest` collapses onto `low`; `highest` reaches `max`.
511
+ const ANTHROPIC_EFFORT = {
512
+ [EFFORT.LOWEST]: { papered: true, value: "low" },
513
+ [EFFORT.LOW]: { papered: false, value: "low" },
514
+ [EFFORT.MEDIUM]: { papered: false, value: "medium" },
515
+ [EFFORT.HIGH]: { papered: false, value: "high" },
516
+ [EFFORT.HIGHEST]: { papered: false, value: "max" },
517
+ };
518
+ function toAnthropicEffort(effort) {
519
+ return ANTHROPIC_EFFORT[effort];
520
+ }
521
+ // Gemini 3.x `thinkingConfig.thinkingLevel` — MINIMAL | LOW | MEDIUM | HIGH. No
522
+ // top rung above HIGH, so `highest` collapses onto HIGH.
523
+ const GEMINI_THINKING_LEVEL = {
524
+ [EFFORT.LOWEST]: { papered: false, value: "MINIMAL" },
525
+ [EFFORT.LOW]: { papered: false, value: "LOW" },
526
+ [EFFORT.MEDIUM]: { papered: false, value: "MEDIUM" },
527
+ [EFFORT.HIGH]: { papered: false, value: "HIGH" },
528
+ [EFFORT.HIGHEST]: { papered: true, value: "HIGH" },
529
+ };
530
+ function toGeminiThinkingLevel(effort) {
531
+ return GEMINI_THINKING_LEVEL[effort];
532
+ }
533
+ // Gemini 2.5 `thinkingConfig.thinkingBudget` — every tier is a distinct token
534
+ // budget, so nothing is papered over. Floor (512) clears every 2.5 minimum;
535
+ // ceiling (24,576) is valid across 2.5 Pro (max 32,768) and Flash (max 24,576).
536
+ const GEMINI_THINKING_BUDGET = {
537
+ [EFFORT.LOWEST]: 512,
538
+ [EFFORT.LOW]: 4096,
539
+ [EFFORT.MEDIUM]: 8192,
540
+ [EFFORT.HIGH]: 16384,
541
+ [EFFORT.HIGHEST]: 24576,
542
+ };
543
+ function toGeminiThinkingBudget(effort) {
544
+ return { papered: false, value: GEMINI_THINKING_BUDGET[effort] };
545
+ }
546
+ // OpenRouter `reasoning.effort` — accepts the full minimal..xhigh ladder and
547
+ // maps to the routed provider's nearest supported level itself, so nothing is
548
+ // papered here.
549
+ const OPENROUTER_EFFORT = {
550
+ [EFFORT.LOWEST]: "minimal",
551
+ [EFFORT.LOW]: "low",
552
+ [EFFORT.MEDIUM]: "medium",
553
+ [EFFORT.HIGH]: "high",
554
+ [EFFORT.HIGHEST]: "xhigh",
555
+ };
556
+ function toOpenRouterEffort(effort) {
557
+ return { papered: false, value: OPENROUTER_EFFORT[effort] };
558
+ }
559
+
464
560
  // Enums
465
561
  var LlmMessageRole;
466
562
  (function (LlmMessageRole) {
@@ -1491,6 +1587,22 @@ function isTransientNetworkError(error) {
1491
1587
  // Constants
1492
1588
  //
1493
1589
  const STRUCTURED_OUTPUT_TOOL_NAME$3 = "structured_output";
1590
+ /**
1591
+ * Whether `output_config.effort` may be sent for this model. Anthropic added
1592
+ * the effort control on the Claude 4.5 line and up (the 5 family); older models
1593
+ * reject it. Parsing major/minor avoids matching a minor "5" in legacy ids
1594
+ * like `claude-3-5-sonnet`.
1595
+ */
1596
+ function supportsAnthropicEffort(model) {
1597
+ const match = model.match(/claude-[a-z]+-(\d+)(?:-(\d+))?/);
1598
+ if (!match)
1599
+ return false;
1600
+ const major = Number(match[1]);
1601
+ const minor = match[2] !== undefined ? Number(match[2]) : 0;
1602
+ if (major >= 5)
1603
+ return true;
1604
+ return major === 4 && minor >= 5;
1605
+ }
1494
1606
  const STRUCTURED_OUTPUT_NON_PARSE_STOP_REASONS = new Set([
1495
1607
  "refusal",
1496
1608
  "max_tokens",
@@ -1756,7 +1868,7 @@ class AnthropicAdapter extends BaseProviderAdapter {
1756
1868
  constructor() {
1757
1869
  super(...arguments);
1758
1870
  this.name = PROVIDER.ANTHROPIC.NAME;
1759
- this.defaultModel = PROVIDER.ANTHROPIC.MODEL.DEFAULT;
1871
+ this.defaultModel = PROVIDER.ANTHROPIC.DEFAULT;
1760
1872
  // Session-level cache of models observed to reject `temperature` at runtime.
1761
1873
  // Populated by executeRequest on 400 errors so repeat calls skip the param.
1762
1874
  this.runtimeNoTemperatureModels = new Set();
@@ -1896,6 +2008,24 @@ class AnthropicAdapter extends BaseProviderAdapter {
1896
2008
  if (request.providerOptions) {
1897
2009
  Object.assign(anthropicRequest, request.providerOptions);
1898
2010
  }
2011
+ // Normalized reasoning effort -> output_config.effort (merged so a format
2012
+ // config above survives). First-class effort wins over providerOptions.
2013
+ if (request.effort &&
2014
+ supportsAnthropicEffort(anthropicRequest.model)) {
2015
+ const mapping = toAnthropicEffort(request.effort);
2016
+ if (mapping.papered) {
2017
+ log$2.debug(paperedEffortMessage({
2018
+ model: anthropicRequest.model,
2019
+ provider: this.name,
2020
+ requested: request.effort,
2021
+ value: mapping.value,
2022
+ }));
2023
+ }
2024
+ anthropicRequest.output_config = {
2025
+ ...anthropicRequest.output_config,
2026
+ effort: mapping.value,
2027
+ };
2028
+ }
1899
2029
  // First-class temperature takes precedence over providerOptions
1900
2030
  if (request.temperature !== undefined) {
1901
2031
  anthropicRequest.temperature = request.temperature;
@@ -1990,9 +2120,13 @@ class AnthropicAdapter extends BaseProviderAdapter {
1990
2120
  */
1991
2121
  toFallbackStructuredOutputRequest(request) {
1992
2122
  const { output_config, ...rest } = request;
1993
- if (!output_config)
2123
+ if (!output_config?.format)
1994
2124
  return request;
1995
2125
  const fallbackRequest = { ...rest };
2126
+ // Preserve an effort setting; only the structured-output format falls back.
2127
+ if (output_config.effort) {
2128
+ fallbackRequest.output_config = { effort: output_config.effort };
2129
+ }
1996
2130
  const fakeTool = {
1997
2131
  name: STRUCTURED_OUTPUT_TOOL_NAME$3,
1998
2132
  description: "Output a structured JSON object, " +
@@ -2436,7 +2570,7 @@ class BedrockAdapter extends BaseProviderAdapter {
2436
2570
  constructor() {
2437
2571
  super(...arguments);
2438
2572
  this.name = PROVIDER.BEDROCK.NAME;
2439
- this.defaultModel = PROVIDER.BEDROCK.MODEL.DEFAULT;
2573
+ this.defaultModel = PROVIDER.BEDROCK.DEFAULT;
2440
2574
  this._modelsFallbackToStructuredOutputTool = new Set();
2441
2575
  this._modelsWithoutTemperature = new Set();
2442
2576
  }
@@ -2942,6 +3076,11 @@ const STRUCTURED_OUTPUT_TOOL_NAME$1 = "structured_output";
2942
3076
  // (including 2.5 thinking) do not support the combo and fall back to the
2943
3077
  // legacy `structured_output` fake-tool emulation.
2944
3078
  const GEMINI_3_PATTERN = /^gemini-3/;
3079
+ // Reasoning-effort control differs by generation: Gemini 3.x takes the
3080
+ // `thinkingLevel` enum, Gemini 2.5 takes an integer `thinkingBudget`. Sending
3081
+ // the wrong one errors, and models outside these families have no thinking
3082
+ // control, so effort is only applied when one of these matches.
3083
+ const GEMINI_25_PATTERN = /^gemini-2\.5/;
2945
3084
  /**
2946
3085
  * Detect 4xx errors that indicate the model itself does not support the
2947
3086
  * `responseJsonSchema` + tools combo. Triggers the runtime fallback to the
@@ -2988,7 +3127,7 @@ class GoogleAdapter extends BaseProviderAdapter {
2988
3127
  constructor() {
2989
3128
  super(...arguments);
2990
3129
  this.name = PROVIDER.GOOGLE.NAME;
2991
- this.defaultModel = PROVIDER.GOOGLE.MODEL.DEFAULT;
3130
+ this.defaultModel = PROVIDER.GOOGLE.DEFAULT;
2992
3131
  // Session-level cache of Gemini 3 models observed to reject the native
2993
3132
  // `responseJsonSchema` + tools combo. When a model is in this set,
2994
3133
  // buildRequest engages the legacy fake-tool path instead.
@@ -3116,6 +3255,42 @@ class GoogleAdapter extends BaseProviderAdapter {
3116
3255
  ...request.providerOptions,
3117
3256
  };
3118
3257
  }
3258
+ // Normalized reasoning effort -> thinkingConfig. Gemini 3.x uses the
3259
+ // thinkingLevel enum; 2.5 uses an integer thinkingBudget. Merged so a
3260
+ // providerOptions.thinkingConfig survives; first-class effort wins.
3261
+ if (request.effort) {
3262
+ const model = geminiRequest.model;
3263
+ let thinkingConfig;
3264
+ let papered = false;
3265
+ let value;
3266
+ if (GEMINI_3_PATTERN.test(model)) {
3267
+ const mapping = toGeminiThinkingLevel(request.effort);
3268
+ ({ papered, value } = mapping);
3269
+ thinkingConfig = { thinkingLevel: mapping.value };
3270
+ }
3271
+ else if (GEMINI_25_PATTERN.test(model)) {
3272
+ const mapping = toGeminiThinkingBudget(request.effort);
3273
+ ({ papered, value } = mapping);
3274
+ thinkingConfig = { thinkingBudget: mapping.value };
3275
+ }
3276
+ if (thinkingConfig) {
3277
+ if (papered) {
3278
+ log$2.debug(paperedEffortMessage({
3279
+ model,
3280
+ provider: this.name,
3281
+ requested: request.effort,
3282
+ value: value,
3283
+ }));
3284
+ }
3285
+ geminiRequest.config = {
3286
+ ...geminiRequest.config,
3287
+ thinkingConfig: {
3288
+ ...geminiRequest.config?.thinkingConfig,
3289
+ ...thinkingConfig,
3290
+ },
3291
+ };
3292
+ }
3293
+ }
3119
3294
  // First-class temperature takes precedence over providerOptions
3120
3295
  if (request.temperature !== undefined) {
3121
3296
  geminiRequest.config = {
@@ -4035,7 +4210,7 @@ class OpenAiAdapter extends BaseProviderAdapter {
4035
4210
  constructor() {
4036
4211
  super(...arguments);
4037
4212
  this.name = PROVIDER.OPENAI.NAME;
4038
- this.defaultModel = PROVIDER.OPENAI.MODEL.DEFAULT;
4213
+ this.defaultModel = PROVIDER.OPENAI.DEFAULT;
4039
4214
  // Session-level cache of models observed to reject `temperature` at runtime.
4040
4215
  // Populated by executeRequest on 400 errors so repeat calls skip the param.
4041
4216
  this.runtimeNoTemperatureModels = new Set();
@@ -4051,6 +4226,14 @@ class OpenAiAdapter extends BaseProviderAdapter {
4051
4226
  return false;
4052
4227
  return !MODELS_WITHOUT_TEMPERATURE$1.some((pattern) => pattern.test(model));
4053
4228
  }
4229
+ /** Whether `reasoning.effort` may be sent for this model. Overridden by xAI. */
4230
+ supportsReasoningEffort(model) {
4231
+ return isReasoningModel(model);
4232
+ }
4233
+ /** Translate a normalized effort to this provider's `reasoning.effort` value. */
4234
+ mapReasoningEffort(effort, model) {
4235
+ return toOpenAiEffort(effort, { model });
4236
+ }
4054
4237
  //
4055
4238
  // Request Building
4056
4239
  //
@@ -4089,6 +4272,24 @@ class OpenAiAdapter extends BaseProviderAdapter {
4089
4272
  if (request.providerOptions) {
4090
4273
  Object.assign(openaiRequest, request.providerOptions);
4091
4274
  }
4275
+ // Normalized reasoning effort -> reasoning.effort (merged so the
4276
+ // summary:auto above survives). First-class effort wins over providerOptions.
4277
+ if (request.effort && this.supportsReasoningEffort(model)) {
4278
+ const mapping = this.mapReasoningEffort(request.effort, model);
4279
+ if (mapping.papered) {
4280
+ log$2.debug(paperedEffortMessage({
4281
+ model,
4282
+ provider: this.name,
4283
+ requested: request.effort,
4284
+ value: mapping.value,
4285
+ }));
4286
+ }
4287
+ const existingReasoning = openaiRequest.reasoning ?? {};
4288
+ openaiRequest.reasoning = {
4289
+ ...existingReasoning,
4290
+ effort: mapping.value,
4291
+ };
4292
+ }
4092
4293
  // First-class temperature takes precedence over providerOptions
4093
4294
  if (request.temperature !== undefined) {
4094
4295
  openaiRequest.temperature = request.temperature;
@@ -4683,7 +4884,7 @@ class OpenRouterAdapter extends BaseProviderAdapter {
4683
4884
  constructor() {
4684
4885
  super(...arguments);
4685
4886
  this.name = PROVIDER.OPENROUTER.NAME;
4686
- this.defaultModel = PROVIDER.OPENROUTER.MODEL.DEFAULT;
4887
+ this.defaultModel = PROVIDER.OPENROUTER.DEFAULT;
4687
4888
  // Session-level cache of models observed to reject native
4688
4889
  // `response_format: json_schema`. When a model is in this set, buildRequest
4689
4890
  // engages the legacy fake-tool path instead of native structured output.
@@ -4776,6 +4977,15 @@ class OpenRouterAdapter extends BaseProviderAdapter {
4776
4977
  if (request.providerOptions) {
4777
4978
  Object.assign(openRouterRequest, request.providerOptions);
4778
4979
  }
4980
+ // Normalized reasoning effort -> reasoning.effort. OpenRouter accepts the
4981
+ // full ladder and maps to the routed provider's nearest supported level.
4982
+ // First-class effort wins over providerOptions.
4983
+ if (request.effort) {
4984
+ openRouterRequest.reasoning = {
4985
+ ...openRouterRequest.reasoning,
4986
+ effort: toOpenRouterEffort(request.effort).value,
4987
+ };
4988
+ }
4779
4989
  // First-class temperature takes precedence over providerOptions
4780
4990
  if (request.temperature !== undefined) {
4781
4991
  openRouterRequest.temperature =
@@ -5394,8 +5604,21 @@ class XaiAdapter extends OpenAiAdapter {
5394
5604
  super(...arguments);
5395
5605
  // @ts-expect-error Narrowing override: xAI name differs from parent's literal "openai"
5396
5606
  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;
5607
+ this.defaultModel = PROVIDER.XAI.DEFAULT;
5608
+ }
5609
+ /**
5610
+ * Grok gates reasoning effort by model, not by the OpenAI `gpt-*`/`o*`
5611
+ * patterns. Only explicit `*-reasoning` models accept `reasoning_effort`;
5612
+ * bare grok-4 reasons implicitly and rejects it, so we stay conservative and
5613
+ * only opt in models whose name advertises reasoning.
5614
+ */
5615
+ supportsReasoningEffort(model) {
5616
+ if (/non-reasoning/.test(model))
5617
+ return false;
5618
+ return /reasoning/.test(model);
5619
+ }
5620
+ mapReasoningEffort(effort) {
5621
+ return toXaiEffort(effort);
5399
5622
  }
5400
5623
  classifyError(error) {
5401
5624
  if (isTransientIngestError(error)) {
@@ -6672,6 +6895,7 @@ class OperateLoop {
6672
6895
  }
6673
6896
  // Rebuild request with updated history for next turn
6674
6897
  request = {
6898
+ effort: options.effort,
6675
6899
  format: state.formattedFormat,
6676
6900
  instructions: options.instructions,
6677
6901
  messages: state.currentInput,
@@ -6775,6 +6999,7 @@ class OperateLoop {
6775
6999
  }
6776
7000
  buildInitialRequest(state, options) {
6777
7001
  return {
7002
+ effort: options.effort,
6778
7003
  format: state.formattedFormat,
6779
7004
  instructions: options.instructions,
6780
7005
  messages: state.currentInput,
@@ -7289,6 +7514,7 @@ class StreamLoop {
7289
7514
  }
7290
7515
  // Rebuild request with updated history for next turn
7291
7516
  request = {
7517
+ effort: options.effort,
7292
7518
  format: state.formattedFormat,
7293
7519
  instructions: options.instructions,
7294
7520
  messages: state.currentInput,
@@ -7364,6 +7590,7 @@ class StreamLoop {
7364
7590
  }
7365
7591
  buildInitialRequest(state, options) {
7366
7592
  return {
7593
+ effort: options.effort,
7367
7594
  format: state.formattedFormat,
7368
7595
  instructions: options.instructions,
7369
7596
  messages: state.currentInput,
@@ -7927,7 +8154,7 @@ async function createStructuredCompletion$1(client, messages, model, responseSch
7927
8154
 
7928
8155
  // Main class implementation
7929
8156
  class AnthropicProvider {
7930
- constructor(model = PROVIDER.ANTHROPIC.MODEL.DEFAULT, { apiKey } = {}) {
8157
+ constructor(model = PROVIDER.ANTHROPIC.DEFAULT, { apiKey } = {}) {
7931
8158
  this.log = getLogger$5();
7932
8159
  this.conversationHistory = [];
7933
8160
  this.model = model;
@@ -8035,7 +8262,7 @@ async function initializeClient$4({ region, } = {}) {
8035
8262
  }
8036
8263
 
8037
8264
  class BedrockProvider {
8038
- constructor(model = PROVIDER.BEDROCK.MODEL.DEFAULT, { region } = {}) {
8265
+ constructor(model = PROVIDER.BEDROCK.DEFAULT, { region } = {}) {
8039
8266
  this.log = getLogger$4();
8040
8267
  this.conversationHistory = [];
8041
8268
  this.model = model;
@@ -8278,7 +8505,7 @@ function prepareMessages$2(message, { data, placeholders } = {}) {
8278
8505
  }
8279
8506
 
8280
8507
  class GoogleProvider {
8281
- constructor(model = PROVIDER.GOOGLE.MODEL.DEFAULT, { apiKey } = {}) {
8508
+ constructor(model = PROVIDER.GOOGLE.DEFAULT, { apiKey } = {}) {
8282
8509
  this.log = getLogger$3();
8283
8510
  this.conversationHistory = [];
8284
8511
  this.model = model;
@@ -8480,7 +8707,7 @@ async function createTextCompletion(client, { messages, model, }) {
8480
8707
  }
8481
8708
 
8482
8709
  class OpenAiProvider {
8483
- constructor(model = PROVIDER.OPENAI.MODEL.DEFAULT, { apiKey } = {}) {
8710
+ constructor(model = PROVIDER.OPENAI.DEFAULT, { apiKey } = {}) {
8484
8711
  this.log = getLogger$2();
8485
8712
  this.conversationHistory = [];
8486
8713
  this.model = model;
@@ -8711,7 +8938,7 @@ async function initializeClient$1({ apiKey, } = {}) {
8711
8938
  }
8712
8939
  // Get default model from environment or constants
8713
8940
  function getDefaultModel() {
8714
- return process.env.OPENROUTER_MODEL || PROVIDER.OPENROUTER.MODEL.DEFAULT;
8941
+ return process.env.OPENROUTER_MODEL || PROVIDER.OPENROUTER.DEFAULT;
8715
8942
  }
8716
8943
  function formatSystemMessage(systemPrompt, { data, placeholders: placeholders$1 } = {}) {
8717
8944
  const content = placeholders$1?.system === false
@@ -8863,7 +9090,7 @@ async function initializeClient({ apiKey, } = {}) {
8863
9090
  }
8864
9091
 
8865
9092
  class XaiProvider {
8866
- constructor(model = PROVIDER.XAI.MODEL.DEFAULT, { apiKey } = {}) {
9093
+ constructor(model = PROVIDER.XAI.DEFAULT, { apiKey } = {}) {
8867
9094
  this.log = getLogger$2();
8868
9095
  this.conversationHistory = [];
8869
9096
  this.model = model;
@@ -8994,23 +9221,25 @@ class Llm {
8994
9221
  const { apiKey, model } = options;
8995
9222
  switch (providerName) {
8996
9223
  case PROVIDER.ANTHROPIC.NAME:
8997
- return new AnthropicProvider(model || PROVIDER.ANTHROPIC.MODEL.DEFAULT, { apiKey });
9224
+ return new AnthropicProvider(model || PROVIDER.ANTHROPIC.DEFAULT, {
9225
+ apiKey,
9226
+ });
8998
9227
  case PROVIDER.BEDROCK.NAME:
8999
- return new BedrockProvider(model || PROVIDER.BEDROCK.MODEL.DEFAULT);
9228
+ return new BedrockProvider(model || PROVIDER.BEDROCK.DEFAULT);
9000
9229
  case PROVIDER.GOOGLE.NAME:
9001
- return new GoogleProvider(model || PROVIDER.GOOGLE.MODEL.DEFAULT, {
9230
+ return new GoogleProvider(model || PROVIDER.GOOGLE.DEFAULT, {
9002
9231
  apiKey,
9003
9232
  });
9004
9233
  case PROVIDER.OPENAI.NAME:
9005
- return new OpenAiProvider(model || PROVIDER.OPENAI.MODEL.DEFAULT, {
9234
+ return new OpenAiProvider(model || PROVIDER.OPENAI.DEFAULT, {
9006
9235
  apiKey,
9007
9236
  });
9008
9237
  case PROVIDER.OPENROUTER.NAME:
9009
- return new OpenRouterProvider(model || PROVIDER.OPENROUTER.MODEL.DEFAULT, {
9238
+ return new OpenRouterProvider(model || PROVIDER.OPENROUTER.DEFAULT, {
9010
9239
  apiKey,
9011
9240
  });
9012
9241
  case PROVIDER.XAI.NAME:
9013
- return new XaiProvider(model || PROVIDER.XAI.MODEL.DEFAULT, {
9242
+ return new XaiProvider(model || PROVIDER.XAI.DEFAULT, {
9014
9243
  apiKey,
9015
9244
  });
9016
9245
  default: