@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.
- package/dist/cjs/Llm.d.ts +19 -11
- package/dist/cjs/constants.d.ts +53 -4
- package/dist/cjs/errors/LlmError.d.ts +62 -0
- package/dist/cjs/errors/toLlmError.d.ts +11 -0
- package/dist/cjs/index.cjs +739 -177
- package/dist/cjs/index.cjs.map +1 -1
- package/dist/cjs/index.d.cts +177 -19
- package/dist/cjs/index.d.ts +5 -1
- package/dist/cjs/operate/adapters/AnthropicAdapter.d.ts +3 -2
- package/dist/cjs/operate/adapters/BedrockAdapter.d.ts +1 -1
- package/dist/cjs/operate/adapters/GoogleAdapter.d.ts +1 -1
- package/dist/cjs/operate/adapters/OpenAiAdapter.d.ts +7 -1
- package/dist/cjs/operate/adapters/OpenRouterAdapter.d.ts +5 -1
- package/dist/cjs/operate/adapters/XaiAdapter.d.ts +11 -1
- package/dist/cjs/operate/retry/RetryExecutor.d.ts +13 -0
- package/dist/cjs/operate/types.d.ts +15 -1
- package/dist/cjs/providers/google/types.d.ts +5 -0
- package/dist/cjs/types/LlmProvider.interface.d.ts +18 -0
- package/dist/cjs/util/classifyProviderError.d.ts +13 -0
- package/dist/cjs/util/effort.d.ts +42 -0
- package/dist/cjs/util/resolveModelChain.d.ts +17 -0
- package/dist/esm/Llm.d.ts +19 -11
- package/dist/esm/constants.d.ts +53 -4
- package/dist/esm/errors/LlmError.d.ts +62 -0
- package/dist/esm/errors/toLlmError.d.ts +11 -0
- package/dist/esm/index.d.ts +177 -19
- package/dist/esm/index.js +705 -148
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/operate/adapters/AnthropicAdapter.d.ts +3 -2
- package/dist/esm/operate/adapters/BedrockAdapter.d.ts +1 -1
- package/dist/esm/operate/adapters/GoogleAdapter.d.ts +1 -1
- package/dist/esm/operate/adapters/OpenAiAdapter.d.ts +7 -1
- package/dist/esm/operate/adapters/OpenRouterAdapter.d.ts +5 -1
- package/dist/esm/operate/adapters/XaiAdapter.d.ts +11 -1
- package/dist/esm/operate/retry/RetryExecutor.d.ts +13 -0
- package/dist/esm/operate/types.d.ts +15 -1
- package/dist/esm/providers/google/types.d.ts +5 -0
- package/dist/esm/types/LlmProvider.interface.d.ts +18 -0
- package/dist/esm/util/classifyProviderError.d.ts +13 -0
- package/dist/esm/util/effort.d.ts +42 -0
- package/dist/esm/util/resolveModelChain.d.ts +17 -0
- 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
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
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:
|
|
66
|
-
LARGE:
|
|
67
|
-
SMALL:
|
|
68
|
-
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:
|
|
108
|
-
LARGE:
|
|
109
|
-
SMALL:
|
|
110
|
-
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:
|
|
140
|
-
LARGE:
|
|
141
|
-
SMALL:
|
|
142
|
-
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", "
|
|
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:
|
|
150
|
-
LARGE:
|
|
151
|
-
SMALL:
|
|
152
|
-
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:
|
|
169
|
-
LARGE:
|
|
170
|
-
SMALL:
|
|
171
|
-
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
319
|
+
model: PROVIDER.XAI.DEFAULT,
|
|
300
320
|
provider: PROVIDER.XAI.NAME,
|
|
301
321
|
};
|
|
302
322
|
}
|
|
303
|
-
//
|
|
304
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
-
|
|
5398
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
9516
|
+
return new AnthropicProvider(model || PROVIDER.ANTHROPIC.DEFAULT, {
|
|
9517
|
+
apiKey,
|
|
9518
|
+
});
|
|
8998
9519
|
case PROVIDER.BEDROCK.NAME:
|
|
8999
|
-
return new BedrockProvider(model || PROVIDER.BEDROCK.
|
|
9520
|
+
return new BedrockProvider(model || PROVIDER.BEDROCK.DEFAULT);
|
|
9000
9521
|
case PROVIDER.GOOGLE.NAME:
|
|
9001
|
-
return new GoogleProvider(model || PROVIDER.GOOGLE.
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
-
|
|
9054
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
9114
|
-
if (!llm &&
|
|
9115
|
-
const determined = determineModelProvider(
|
|
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 &&
|
|
9668
|
+
else if (llm && primaryModel) {
|
|
9121
9669
|
// When both llm and model are provided, check if they conflict
|
|
9122
|
-
const determined = determineModelProvider(
|
|
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
|
|
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 =
|
|
9145
|
-
if (!llm &&
|
|
9146
|
-
const determined = determineModelProvider(
|
|
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 &&
|
|
9704
|
+
else if (llm && primaryModel) {
|
|
9152
9705
|
// When both llm and model are provided, check if they conflict
|
|
9153
|
-
const determined = determineModelProvider(
|
|
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, {
|
|
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
|