@jaypie/llm 1.3.0 → 1.3.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (87) hide show
  1. package/README.md +9 -0
  2. package/dist/cjs/constants.d.ts +82 -30
  3. package/dist/cjs/index.cjs +3081 -578
  4. package/dist/cjs/index.cjs.map +1 -1
  5. package/dist/cjs/index.d.cts +661 -0
  6. package/dist/cjs/index.d.ts +5 -1
  7. package/dist/cjs/observability/llmobs.d.ts +72 -0
  8. package/dist/cjs/operate/OperateLoop.d.ts +7 -0
  9. package/dist/cjs/operate/StreamLoop.d.ts +3 -0
  10. package/dist/cjs/operate/adapters/AnthropicAdapter.d.ts +36 -5
  11. package/dist/cjs/operate/adapters/BedrockAdapter.d.ts +78 -0
  12. package/dist/cjs/operate/adapters/{GeminiAdapter.d.ts → GoogleAdapter.d.ts} +19 -9
  13. package/dist/cjs/operate/adapters/OpenAiAdapter.d.ts +13 -3
  14. package/dist/cjs/operate/adapters/OpenRouterAdapter.d.ts +55 -8
  15. package/dist/cjs/operate/adapters/ProviderAdapter.interface.d.ts +5 -3
  16. package/dist/cjs/operate/adapters/XaiAdapter.d.ts +14 -0
  17. package/dist/cjs/operate/adapters/index.d.ts +3 -1
  18. package/dist/cjs/operate/index.d.ts +1 -1
  19. package/dist/cjs/operate/retry/RetryExecutor.d.ts +6 -3
  20. package/dist/cjs/operate/retry/createStaleRejectionGuard.d.ts +19 -0
  21. package/dist/cjs/operate/retry/index.d.ts +1 -0
  22. package/dist/cjs/operate/retry/isTransientNetworkError.d.ts +18 -0
  23. package/dist/cjs/operate/types.d.ts +2 -0
  24. package/dist/cjs/providers/anthropic/utils.d.ts +2 -1
  25. package/dist/cjs/providers/bedrock/BedrockProvider.class.d.ts +21 -0
  26. package/dist/cjs/providers/bedrock/index.d.ts +1 -0
  27. package/dist/cjs/providers/bedrock/utils.d.ts +7 -0
  28. package/dist/cjs/providers/{gemini/GeminiProvider.class.d.ts → google/GoogleProvider.class.d.ts} +1 -1
  29. package/dist/cjs/providers/google/index.d.ts +3 -0
  30. package/dist/{esm/providers/gemini → cjs/providers/google}/types.d.ts +1 -0
  31. package/dist/cjs/providers/{gemini → google}/utils.d.ts +2 -1
  32. package/dist/cjs/providers/openai/utils.d.ts +2 -1
  33. package/dist/cjs/providers/openrouter/utils.d.ts +2 -1
  34. package/dist/{esm/providers/gemini/GeminiProvider.class.d.ts → cjs/providers/xai/XaiProvider.class.d.ts} +1 -1
  35. package/dist/cjs/providers/xai/index.d.ts +1 -0
  36. package/dist/cjs/providers/xai/utils.d.ts +6 -0
  37. package/dist/cjs/types/LlmProvider.interface.d.ts +1 -1
  38. package/dist/cjs/types/LlmStreamChunk.interface.d.ts +2 -0
  39. package/dist/cjs/util/fillFormatArrays.d.ts +17 -0
  40. package/dist/cjs/util/index.d.ts +2 -0
  41. package/dist/cjs/util/jsonSchemaToOpenApi3.d.ts +10 -0
  42. package/dist/cjs/util/logger.d.ts +2 -2
  43. package/dist/cjs/util/maxTurnsFromOptions.d.ts +1 -1
  44. package/dist/esm/constants.d.ts +82 -30
  45. package/dist/esm/index.d.ts +661 -12
  46. package/dist/esm/index.js +3077 -578
  47. package/dist/esm/index.js.map +1 -1
  48. package/dist/esm/observability/llmobs.d.ts +72 -0
  49. package/dist/esm/operate/OperateLoop.d.ts +7 -0
  50. package/dist/esm/operate/StreamLoop.d.ts +3 -0
  51. package/dist/esm/operate/adapters/AnthropicAdapter.d.ts +36 -5
  52. package/dist/esm/operate/adapters/BedrockAdapter.d.ts +78 -0
  53. package/dist/esm/operate/adapters/{GeminiAdapter.d.ts → GoogleAdapter.d.ts} +19 -9
  54. package/dist/esm/operate/adapters/OpenAiAdapter.d.ts +13 -3
  55. package/dist/esm/operate/adapters/OpenRouterAdapter.d.ts +55 -8
  56. package/dist/esm/operate/adapters/ProviderAdapter.interface.d.ts +5 -3
  57. package/dist/esm/operate/adapters/XaiAdapter.d.ts +14 -0
  58. package/dist/esm/operate/adapters/index.d.ts +3 -1
  59. package/dist/esm/operate/index.d.ts +1 -1
  60. package/dist/esm/operate/retry/RetryExecutor.d.ts +6 -3
  61. package/dist/esm/operate/retry/createStaleRejectionGuard.d.ts +19 -0
  62. package/dist/esm/operate/retry/index.d.ts +1 -0
  63. package/dist/esm/operate/retry/isTransientNetworkError.d.ts +18 -0
  64. package/dist/esm/operate/types.d.ts +2 -0
  65. package/dist/esm/providers/anthropic/utils.d.ts +2 -1
  66. package/dist/esm/providers/bedrock/BedrockProvider.class.d.ts +21 -0
  67. package/dist/esm/providers/bedrock/index.d.ts +1 -0
  68. package/dist/esm/providers/bedrock/utils.d.ts +7 -0
  69. package/dist/esm/providers/google/GoogleProvider.class.d.ts +21 -0
  70. package/dist/esm/providers/google/index.d.ts +3 -0
  71. package/dist/{cjs/providers/gemini → esm/providers/google}/types.d.ts +1 -0
  72. package/dist/esm/providers/{gemini → google}/utils.d.ts +2 -1
  73. package/dist/esm/providers/openai/utils.d.ts +2 -1
  74. package/dist/esm/providers/openrouter/utils.d.ts +2 -1
  75. package/dist/esm/providers/xai/XaiProvider.class.d.ts +21 -0
  76. package/dist/esm/providers/xai/index.d.ts +1 -0
  77. package/dist/esm/providers/xai/utils.d.ts +6 -0
  78. package/dist/esm/types/LlmProvider.interface.d.ts +1 -1
  79. package/dist/esm/types/LlmStreamChunk.interface.d.ts +2 -0
  80. package/dist/esm/util/fillFormatArrays.d.ts +17 -0
  81. package/dist/esm/util/index.d.ts +2 -0
  82. package/dist/esm/util/jsonSchemaToOpenApi3.d.ts +10 -0
  83. package/dist/esm/util/logger.d.ts +2 -2
  84. package/dist/esm/util/maxTurnsFromOptions.d.ts +1 -1
  85. package/package.json +18 -5
  86. package/dist/cjs/providers/gemini/index.d.ts +0 -3
  87. package/dist/esm/providers/gemini/index.d.ts +0 -3
@@ -1,45 +1,105 @@
1
1
  'use strict';
2
2
 
3
3
  var errors = require('@jaypie/errors');
4
- var log$2 = require('@jaypie/logger');
4
+ var log$1 = require('@jaypie/logger');
5
5
  var v4 = require('zod/v4');
6
6
  var kit = require('@jaypie/kit');
7
7
  var RandomLib = require('random');
8
8
  var openai = require('openai');
9
9
  var zod = require('openai/helpers/zod');
10
+ var module$1 = require('module');
11
+ var url = require('url');
10
12
  var pdfLib = require('pdf-lib');
11
13
  var promises = require('fs/promises');
12
14
  var path = require('path');
13
15
  var aws = require('@jaypie/aws');
14
16
  var openmeteo = require('openmeteo');
15
17
 
18
+ var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null;
16
19
  const FIRST_CLASS_PROVIDER = {
20
+ // https://docs.anthropic.com/en/docs/about-claude/models/overview
17
21
  ANTHROPIC: {
18
- DEFAULT: "claude-sonnet-4-5",
19
- LARGE: "claude-opus-4-5",
20
- SMALL: "claude-sonnet-4-5",
22
+ DEFAULT: "claude-sonnet-4-6",
23
+ LARGE: "claude-opus-4-8",
24
+ SMALL: "claude-sonnet-4-6",
21
25
  TINY: "claude-haiku-4-5",
22
26
  },
23
- GEMINI: {
24
- DEFAULT: "gemini-3-pro-preview",
25
- LARGE: "gemini-3-pro-preview",
26
- SMALL: "gemini-3-flash-preview",
27
- TINY: "gemini-3-flash-preview",
27
+ // https://ai.google.dev/gemini-api/docs/models
28
+ GOOGLE: {
29
+ DEFAULT: "gemini-3.1-pro-preview",
30
+ LARGE: "gemini-3.1-pro-preview",
31
+ SMALL: "gemini-3.5-flash",
32
+ TINY: "gemini-3.1-flash-lite",
28
33
  },
34
+ // https://developers.openai.com/api/docs/models
29
35
  OPENAI: {
30
- DEFAULT: "gpt-5.2",
31
- LARGE: "gpt-5.2-pro",
32
- SMALL: "gpt-5-mini",
33
- TINY: "gpt-5-nano",
36
+ DEFAULT: "gpt-5.4",
37
+ LARGE: "gpt-5.5",
38
+ SMALL: "gpt-5.4-mini",
39
+ TINY: "gpt-5.4-nano",
34
40
  },
35
- OPENROUTER: {
36
- DEFAULT: "z-ai/glm-4.7",
37
- LARGE: "z-ai/glm-4.7",
38
- SMALL: "z-ai/glm-4.7",
39
- TINY: "z-ai/glm-4.7",
41
+ // https://docs.x.ai/developers/models
42
+ XAI: {
43
+ DEFAULT: "grok-latest",
44
+ LARGE: "grok-4.3-latest",
45
+ SMALL: "grok-4-1-fast-reasoning",
46
+ TINY: "grok-4-1-fast-non-reasoning",
47
+ },
48
+ };
49
+ const MODEL = {
50
+ // Anthropic
51
+ OPUS: "claude-opus-4-8",
52
+ SONNET: "claude-sonnet-4-6",
53
+ HAIKU: "claude-haiku-4-5",
54
+ FABLE: "claude-fable-5",
55
+ MYTHOS: "claude-mythos-5",
56
+ // Google
57
+ GEMINI_FLASH: "gemini-3.5-flash",
58
+ GEMINI_FLASH_LITE: "gemini-3.1-flash-lite",
59
+ GEMINI_PRO: "gemini-3.1-pro-preview",
60
+ // OpenAI
61
+ GPT: "gpt-5.5",
62
+ GPT_MINI: "gpt-5.4-mini",
63
+ GPT_NANO: "gpt-5.4-nano",
64
+ // xAI
65
+ GROK: "grok-latest",
66
+ };
67
+ const GOOGLE_PROVIDER = {
68
+ // https://ai.google.dev/gemini-api/docs/models
69
+ MODEL: {
70
+ DEFAULT: FIRST_CLASS_PROVIDER.GOOGLE.DEFAULT,
71
+ LARGE: FIRST_CLASS_PROVIDER.GOOGLE.LARGE,
72
+ SMALL: FIRST_CLASS_PROVIDER.GOOGLE.SMALL,
73
+ TINY: FIRST_CLASS_PROVIDER.GOOGLE.TINY,
74
+ },
75
+ MODEL_MATCH_WORDS: ["gemini", "google"],
76
+ NAME: "google",
77
+ ROLE: {
78
+ MODEL: "model",
79
+ USER: "user",
40
80
  },
41
81
  };
42
82
  const PROVIDER = {
83
+ // https://docs.aws.amazon.com/bedrock/latest/userguide/models-supported.html
84
+ BEDROCK: {
85
+ MODEL: {
86
+ DEFAULT: "amazon.nova-lite-v1:0",
87
+ LARGE: "amazon.nova-pro-v1:0",
88
+ SMALL: "amazon.nova-lite-v1:0",
89
+ TINY: "amazon.nova-micro-v1:0",
90
+ },
91
+ MODEL_MATCH_WORDS: [
92
+ "amazon.nova",
93
+ "amazon.titan",
94
+ "anthropic.claude",
95
+ "cohere.command",
96
+ "meta.llama",
97
+ "mistral.mistral",
98
+ "ai21.",
99
+ ],
100
+ NAME: "bedrock",
101
+ REGION: "AWS_REGION",
102
+ },
43
103
  ANTHROPIC: {
44
104
  // https://docs.anthropic.com/en/docs/about-claude/models/overview
45
105
  MAX_TOKENS: {
@@ -72,21 +132,9 @@ const PROVIDER = {
72
132
  SCHEMA_VERSION: "v2",
73
133
  },
74
134
  },
75
- GEMINI: {
76
- // https://ai.google.dev/gemini-api/docs/models
77
- MODEL: {
78
- DEFAULT: FIRST_CLASS_PROVIDER.GEMINI.DEFAULT,
79
- LARGE: FIRST_CLASS_PROVIDER.GEMINI.LARGE,
80
- SMALL: FIRST_CLASS_PROVIDER.GEMINI.SMALL,
81
- TINY: FIRST_CLASS_PROVIDER.GEMINI.TINY,
82
- },
83
- MODEL_MATCH_WORDS: ["gemini", "google"],
84
- NAME: "gemini",
85
- ROLE: {
86
- MODEL: "model",
87
- USER: "user",
88
- },
89
- },
135
+ /** @deprecated Use PROVIDER.GOOGLE — "Google" is the provider; Gemini is the model family */
136
+ GEMINI: GOOGLE_PROVIDER,
137
+ GOOGLE: GOOGLE_PROVIDER,
90
138
  OPENAI: {
91
139
  // https://platform.openai.com/docs/models
92
140
  MODEL: {
@@ -99,15 +147,11 @@ const PROVIDER = {
99
147
  NAME: "openai",
100
148
  },
101
149
  OPENROUTER: {
102
- // https://openrouter.ai/models
103
- // OpenRouter provides access to hundreds of models from various providers
104
- // The model format is: provider/model-name (e.g., "openai/gpt-4", "anthropic/claude-3-opus")
105
150
  MODEL: {
106
- // Default uses env var OPENROUTER_MODEL if set, otherwise a reasonable default
107
- DEFAULT: FIRST_CLASS_PROVIDER.OPENROUTER.DEFAULT,
108
- LARGE: FIRST_CLASS_PROVIDER.OPENROUTER.LARGE,
109
- SMALL: FIRST_CLASS_PROVIDER.OPENROUTER.SMALL,
110
- TINY: FIRST_CLASS_PROVIDER.OPENROUTER.TINY,
151
+ DEFAULT: `anthropic/${FIRST_CLASS_PROVIDER.ANTHROPIC.DEFAULT}`,
152
+ LARGE: `anthropic/${FIRST_CLASS_PROVIDER.ANTHROPIC.LARGE}`,
153
+ SMALL: `anthropic/${FIRST_CLASS_PROVIDER.ANTHROPIC.SMALL}`,
154
+ TINY: `anthropic/${FIRST_CLASS_PROVIDER.ANTHROPIC.TINY}`,
111
155
  },
112
156
  MODEL_MATCH_WORDS: ["openrouter"],
113
157
  NAME: "openrouter",
@@ -118,6 +162,19 @@ const PROVIDER = {
118
162
  USER: "user",
119
163
  },
120
164
  },
165
+ XAI: {
166
+ // https://docs.x.ai/docs/models
167
+ API_KEY: "XAI_API_KEY",
168
+ BASE_URL: "https://api.x.ai/v1",
169
+ MODEL: {
170
+ DEFAULT: FIRST_CLASS_PROVIDER.XAI.DEFAULT,
171
+ LARGE: FIRST_CLASS_PROVIDER.XAI.LARGE,
172
+ SMALL: FIRST_CLASS_PROVIDER.XAI.SMALL,
173
+ TINY: FIRST_CLASS_PROVIDER.XAI.TINY,
174
+ },
175
+ MODEL_MATCH_WORDS: ["grok", "xai"],
176
+ NAME: "xai",
177
+ },
121
178
  };
122
179
  // Last: Defaults
123
180
  const DEFAULT = {
@@ -133,37 +190,47 @@ const DEFAULT = {
133
190
  const ALL = {
134
191
  BASE: [
135
192
  PROVIDER.ANTHROPIC.MODEL.DEFAULT,
136
- PROVIDER.GEMINI.MODEL.DEFAULT,
193
+ PROVIDER.GOOGLE.MODEL.DEFAULT,
137
194
  PROVIDER.OPENAI.MODEL.DEFAULT,
195
+ PROVIDER.XAI.MODEL.DEFAULT,
138
196
  ],
139
197
  COMBINED: [
140
- PROVIDER.ANTHROPIC.MODEL.DEFAULT,
141
- PROVIDER.ANTHROPIC.MODEL.LARGE,
142
- PROVIDER.ANTHROPIC.MODEL.SMALL,
143
- PROVIDER.ANTHROPIC.MODEL.TINY,
144
- PROVIDER.GEMINI.MODEL.DEFAULT,
145
- PROVIDER.GEMINI.MODEL.LARGE,
146
- PROVIDER.GEMINI.MODEL.SMALL,
147
- PROVIDER.GEMINI.MODEL.TINY,
148
- PROVIDER.OPENAI.MODEL.DEFAULT,
149
- PROVIDER.OPENAI.MODEL.LARGE,
150
- PROVIDER.OPENAI.MODEL.SMALL,
151
- PROVIDER.OPENAI.MODEL.TINY,
198
+ ...new Set([
199
+ PROVIDER.ANTHROPIC.MODEL.DEFAULT,
200
+ PROVIDER.ANTHROPIC.MODEL.LARGE,
201
+ PROVIDER.ANTHROPIC.MODEL.SMALL,
202
+ PROVIDER.ANTHROPIC.MODEL.TINY,
203
+ PROVIDER.GOOGLE.MODEL.DEFAULT,
204
+ PROVIDER.GOOGLE.MODEL.LARGE,
205
+ PROVIDER.GOOGLE.MODEL.SMALL,
206
+ PROVIDER.GOOGLE.MODEL.TINY,
207
+ PROVIDER.OPENAI.MODEL.DEFAULT,
208
+ PROVIDER.OPENAI.MODEL.LARGE,
209
+ PROVIDER.OPENAI.MODEL.SMALL,
210
+ PROVIDER.OPENAI.MODEL.TINY,
211
+ PROVIDER.XAI.MODEL.DEFAULT,
212
+ PROVIDER.XAI.MODEL.LARGE,
213
+ PROVIDER.XAI.MODEL.SMALL,
214
+ PROVIDER.XAI.MODEL.TINY,
215
+ ]),
152
216
  ],
153
217
  LARGE: [
154
218
  PROVIDER.ANTHROPIC.MODEL.LARGE,
155
- PROVIDER.GEMINI.MODEL.LARGE,
219
+ PROVIDER.GOOGLE.MODEL.LARGE,
156
220
  PROVIDER.OPENAI.MODEL.LARGE,
221
+ PROVIDER.XAI.MODEL.LARGE,
157
222
  ],
158
223
  SMALL: [
159
224
  PROVIDER.ANTHROPIC.MODEL.SMALL,
160
- PROVIDER.GEMINI.MODEL.SMALL,
225
+ PROVIDER.GOOGLE.MODEL.SMALL,
161
226
  PROVIDER.OPENAI.MODEL.SMALL,
227
+ PROVIDER.XAI.MODEL.SMALL,
162
228
  ],
163
229
  TINY: [
164
230
  PROVIDER.ANTHROPIC.MODEL.TINY,
165
- PROVIDER.GEMINI.MODEL.TINY,
231
+ PROVIDER.GOOGLE.MODEL.TINY,
166
232
  PROVIDER.OPENAI.MODEL.TINY,
233
+ PROVIDER.XAI.MODEL.TINY,
167
234
  ],
168
235
  };
169
236
 
@@ -171,6 +238,7 @@ var constants = /*#__PURE__*/Object.freeze({
171
238
  __proto__: null,
172
239
  ALL: ALL,
173
240
  DEFAULT: DEFAULT,
241
+ MODEL: MODEL,
174
242
  PROVIDER: PROVIDER
175
243
  });
176
244
 
@@ -189,17 +257,31 @@ function determineModelProvider(input) {
189
257
  provider: PROVIDER.OPENROUTER.NAME,
190
258
  };
191
259
  }
260
+ // Check for explicit bedrock: prefix
261
+ if (input.startsWith("bedrock:")) {
262
+ const model = input.slice("bedrock:".length);
263
+ return {
264
+ model,
265
+ provider: PROVIDER.BEDROCK.NAME,
266
+ };
267
+ }
192
268
  // Check if input is a provider name
269
+ if (input === PROVIDER.BEDROCK.NAME) {
270
+ return {
271
+ model: PROVIDER.BEDROCK.MODEL.DEFAULT,
272
+ provider: PROVIDER.BEDROCK.NAME,
273
+ };
274
+ }
193
275
  if (input === PROVIDER.ANTHROPIC.NAME) {
194
276
  return {
195
277
  model: PROVIDER.ANTHROPIC.MODEL.DEFAULT,
196
278
  provider: PROVIDER.ANTHROPIC.NAME,
197
279
  };
198
280
  }
199
- if (input === PROVIDER.GEMINI.NAME) {
281
+ if (input === PROVIDER.GOOGLE.NAME || input === "gemini") {
200
282
  return {
201
- model: PROVIDER.GEMINI.MODEL.DEFAULT,
202
- provider: PROVIDER.GEMINI.NAME,
283
+ model: PROVIDER.GOOGLE.MODEL.DEFAULT,
284
+ provider: PROVIDER.GOOGLE.NAME,
203
285
  };
204
286
  }
205
287
  if (input === PROVIDER.OPENAI.NAME) {
@@ -214,6 +296,12 @@ function determineModelProvider(input) {
214
296
  provider: PROVIDER.OPENROUTER.NAME,
215
297
  };
216
298
  }
299
+ if (input === PROVIDER.XAI.NAME) {
300
+ return {
301
+ model: PROVIDER.XAI.MODEL.DEFAULT,
302
+ provider: PROVIDER.XAI.NAME,
303
+ };
304
+ }
217
305
  // Check if input matches an Anthropic model exactly
218
306
  for (const [, modelValue] of Object.entries(PROVIDER.ANTHROPIC.MODEL)) {
219
307
  if (input === modelValue) {
@@ -224,11 +312,11 @@ function determineModelProvider(input) {
224
312
  }
225
313
  }
226
314
  // Check if input matches a Gemini model exactly
227
- for (const [, modelValue] of Object.entries(PROVIDER.GEMINI.MODEL)) {
315
+ for (const [, modelValue] of Object.entries(PROVIDER.GOOGLE.MODEL)) {
228
316
  if (input === modelValue) {
229
317
  return {
230
318
  model: input,
231
- provider: PROVIDER.GEMINI.NAME,
319
+ provider: PROVIDER.GOOGLE.NAME,
232
320
  };
233
321
  }
234
322
  }
@@ -250,6 +338,15 @@ function determineModelProvider(input) {
250
338
  };
251
339
  }
252
340
  }
341
+ // Check if input matches an xAI model exactly
342
+ for (const [, modelValue] of Object.entries(PROVIDER.XAI.MODEL)) {
343
+ if (input === modelValue) {
344
+ return {
345
+ model: input,
346
+ provider: PROVIDER.XAI.NAME,
347
+ };
348
+ }
349
+ }
253
350
  // Assume OpenRouter for models containing "/" (e.g., "openai/gpt-4", "anthropic/claude-3-opus")
254
351
  // This check must come before match words so that "openai/gpt-4" is not matched by "openai" keyword
255
352
  if (input.includes("/")) {
@@ -258,8 +355,17 @@ function determineModelProvider(input) {
258
355
  provider: PROVIDER.OPENROUTER.NAME,
259
356
  };
260
357
  }
261
- // Check Anthropic match words
358
+ // Check Bedrock match words (before Anthropic — "anthropic.claude-*" is a Bedrock model ID)
262
359
  const lowerInput = input.toLowerCase();
360
+ for (const matchWord of PROVIDER.BEDROCK.MODEL_MATCH_WORDS) {
361
+ if (lowerInput.includes(matchWord)) {
362
+ return {
363
+ model: input,
364
+ provider: PROVIDER.BEDROCK.NAME,
365
+ };
366
+ }
367
+ }
368
+ // Check Anthropic match words
263
369
  for (const matchWord of PROVIDER.ANTHROPIC.MODEL_MATCH_WORDS) {
264
370
  if (lowerInput.includes(matchWord)) {
265
371
  return {
@@ -269,11 +375,11 @@ function determineModelProvider(input) {
269
375
  }
270
376
  }
271
377
  // Check Gemini match words
272
- for (const matchWord of PROVIDER.GEMINI.MODEL_MATCH_WORDS) {
378
+ for (const matchWord of PROVIDER.GOOGLE.MODEL_MATCH_WORDS) {
273
379
  if (lowerInput.includes(matchWord)) {
274
380
  return {
275
381
  model: input,
276
- provider: PROVIDER.GEMINI.NAME,
382
+ provider: PROVIDER.GOOGLE.NAME,
277
383
  };
278
384
  }
279
385
  }
@@ -296,6 +402,15 @@ function determineModelProvider(input) {
296
402
  }
297
403
  }
298
404
  }
405
+ // Check xAI match words
406
+ for (const matchWord of PROVIDER.XAI.MODEL_MATCH_WORDS) {
407
+ if (lowerInput.includes(matchWord)) {
408
+ return {
409
+ model: input,
410
+ provider: PROVIDER.XAI.NAME,
411
+ };
412
+ }
413
+ }
299
414
  // Check OpenRouter match words
300
415
  for (const matchWord of PROVIDER.OPENROUTER.MODEL_MATCH_WORDS) {
301
416
  if (lowerInput.includes(matchWord)) {
@@ -460,6 +575,154 @@ function extractReasoning(history) {
460
575
  return reasoningTexts;
461
576
  }
462
577
 
578
+ function naturalZodSchema(definition) {
579
+ if (Array.isArray(definition)) {
580
+ if (definition.length === 0) {
581
+ // Handle empty array - accept any[]
582
+ return v4.z.array(v4.z.any());
583
+ }
584
+ else if (definition.length === 1) {
585
+ // Handle array types
586
+ const itemType = definition[0];
587
+ switch (itemType) {
588
+ case String:
589
+ return v4.z.array(v4.z.string());
590
+ case Number:
591
+ return v4.z.array(v4.z.number());
592
+ case Boolean:
593
+ return v4.z.array(v4.z.boolean());
594
+ default:
595
+ if (typeof itemType === "object") {
596
+ // Handle array of objects
597
+ return v4.z.array(naturalZodSchema(itemType));
598
+ }
599
+ // Handle enum arrays
600
+ return v4.z.enum(definition);
601
+ }
602
+ }
603
+ else {
604
+ // Handle enum arrays
605
+ return v4.z.enum(definition);
606
+ }
607
+ }
608
+ else if (definition && typeof definition === "object") {
609
+ if (Object.keys(definition).length === 0) {
610
+ // Handle empty object - accept any key-value pairs
611
+ return v4.z.record(v4.z.string(), v4.z.any());
612
+ }
613
+ else {
614
+ // Handle object with properties
615
+ const schemaShape = {};
616
+ for (const [key, value] of Object.entries(definition)) {
617
+ schemaShape[key] = naturalZodSchema(value);
618
+ }
619
+ return v4.z.object(schemaShape);
620
+ }
621
+ }
622
+ else {
623
+ switch (definition) {
624
+ case String:
625
+ return v4.z.string();
626
+ case Number:
627
+ return v4.z.number();
628
+ case Boolean:
629
+ return v4.z.boolean();
630
+ case Object:
631
+ return v4.z.record(v4.z.string(), v4.z.any());
632
+ case Array:
633
+ return v4.z.array(v4.z.any());
634
+ default:
635
+ throw new Error(`Unsupported type: ${definition}`);
636
+ }
637
+ }
638
+ }
639
+
640
+ //
641
+ //
642
+ // Helpers
643
+ //
644
+ function isPlainObject(value) {
645
+ return typeof value === "object" && value !== null && !Array.isArray(value);
646
+ }
647
+ /**
648
+ * Convert a `format` declaration (Zod schema, NaturalSchema, or a JSON Schema
649
+ * JsonObject) into a plain JSON Schema we can walk. Mirrors the conversion the
650
+ * provider adapters perform in `formatOutputSchema`, but without any
651
+ * provider-specific sanitization.
652
+ */
653
+ function formatToJsonSchema(format) {
654
+ if (format instanceof v4.z.ZodType) {
655
+ return v4.z.toJSONSchema(format);
656
+ }
657
+ if (isPlainObject(format) && format.type === "json_schema") {
658
+ const clone = structuredClone(format);
659
+ clone.type = "object";
660
+ return clone;
661
+ }
662
+ try {
663
+ return v4.z.toJSONSchema(naturalZodSchema(format));
664
+ }
665
+ catch {
666
+ return undefined;
667
+ }
668
+ }
669
+ /**
670
+ * Walk a JSON Schema alongside a parsed value, filling any declared array field
671
+ * that is absent (`undefined`/`null`) with `[]`. Recurses into object
672
+ * properties and array items so nested declared arrays are also backfilled.
673
+ */
674
+ function fillFromSchema(schema, value) {
675
+ if (!isPlainObject(schema)) {
676
+ return value;
677
+ }
678
+ const type = schema.type;
679
+ const isArray = type === "array" || (type === undefined && "items" in schema);
680
+ if (isArray) {
681
+ if (value === undefined || value === null) {
682
+ return [];
683
+ }
684
+ const items = schema.items;
685
+ if (Array.isArray(value) && isPlainObject(items)) {
686
+ return value.map((entry) => fillFromSchema(items, entry));
687
+ }
688
+ return value;
689
+ }
690
+ const isObject = type === "object" || (type === undefined && "properties" in schema);
691
+ if (isObject) {
692
+ if (!isPlainObject(value)) {
693
+ return value;
694
+ }
695
+ const properties = schema.properties;
696
+ if (isPlainObject(properties)) {
697
+ for (const [key, propSchema] of Object.entries(properties)) {
698
+ value[key] = fillFromSchema(propSchema, value[key]);
699
+ }
700
+ }
701
+ return value;
702
+ }
703
+ return value;
704
+ }
705
+ //
706
+ //
707
+ // Main
708
+ //
709
+ /**
710
+ * Ensure every array field declared in `format` is present in `content` as an
711
+ * array. A declared `format` is a schema contract: an empty list should surface
712
+ * as `[]`, not be dropped from the response. Some providers/models omit empty
713
+ * array fields entirely, leaving consumers to read `.length` on `undefined`.
714
+ *
715
+ * Only mutates a (cloned) structured object; strings and non-objects pass
716
+ * through untouched.
717
+ */
718
+ function fillFormatArrays({ content, format, }) {
719
+ const schema = formatToJsonSchema(format);
720
+ if (!schema) {
721
+ return content;
722
+ }
723
+ return fillFromSchema(schema, structuredClone(content));
724
+ }
725
+
463
726
  /**
464
727
  * Converts a string to a standardized LlmInputMessage
465
728
  * @param input - String to format
@@ -507,12 +770,53 @@ function formatOperateInput(input, options) {
507
770
  return [input];
508
771
  }
509
772
 
510
- const getLogger$4 = () => log$2.log.lib({ lib: kit.JAYPIE.LIB.LLM });
511
- const log$1 = getLogger$4();
773
+ /**
774
+ * Converts a JSON Schema (Draft 2020-12) object to the OpenAPI 3.0 schema subset
775
+ * that Gemini's `responseSchema` accepts. This constrains generation (not just validation)
776
+ * and avoids the `items`-keyword leakage bug in `responseJsonSchema`.
777
+ *
778
+ * Strips: $schema, additionalProperties, $defs, $ref (inlines where possible), const
779
+ * Preserves: type, properties, required, items, enum, description, nullable
780
+ */
781
+ function jsonSchemaToOpenApi3(schema) {
782
+ if (typeof schema !== "object" || schema === null || Array.isArray(schema)) {
783
+ return schema;
784
+ }
785
+ const result = {};
786
+ for (const [key, value] of Object.entries(schema)) {
787
+ // Strip JSON Schema keywords not in OpenAPI 3.0 subset
788
+ if (key === "$schema" ||
789
+ key === "$defs" ||
790
+ key === "additionalProperties" ||
791
+ key === "const" ||
792
+ key === "$ref") {
793
+ continue;
794
+ }
795
+ if (key === "properties" &&
796
+ typeof value === "object" &&
797
+ value !== null &&
798
+ !Array.isArray(value)) {
799
+ const convertedProps = {};
800
+ for (const [propKey, propValue] of Object.entries(value)) {
801
+ convertedProps[propKey] = jsonSchemaToOpenApi3(propValue);
802
+ }
803
+ result[key] = convertedProps;
804
+ }
805
+ else if (key === "items" && typeof value === "object" && value !== null) {
806
+ result[key] = jsonSchemaToOpenApi3(value);
807
+ }
808
+ else {
809
+ result[key] = value;
810
+ }
811
+ }
812
+ return result;
813
+ }
814
+
815
+ const getLogger$6 = () => log$1.log.lib({ lib: kit.JAYPIE.LIB.LLM });
512
816
 
513
817
  // Turn policy constants
514
818
  const MAX_TURNS_ABSOLUTE_LIMIT = 72;
515
- const MAX_TURNS_DEFAULT_LIMIT = 12;
819
+ const MAX_TURNS_DEFAULT_LIMIT = 24;
516
820
  /**
517
821
  * Determines the maximum number of turns based on the provided options
518
822
  *
@@ -546,68 +850,6 @@ function maxTurnsFromOptions(options) {
546
850
  return 1;
547
851
  }
548
852
 
549
- function naturalZodSchema(definition) {
550
- if (Array.isArray(definition)) {
551
- if (definition.length === 0) {
552
- // Handle empty array - accept any[]
553
- return v4.z.array(v4.z.any());
554
- }
555
- else if (definition.length === 1) {
556
- // Handle array types
557
- const itemType = definition[0];
558
- switch (itemType) {
559
- case String:
560
- return v4.z.array(v4.z.string());
561
- case Number:
562
- return v4.z.array(v4.z.number());
563
- case Boolean:
564
- return v4.z.array(v4.z.boolean());
565
- default:
566
- if (typeof itemType === "object") {
567
- // Handle array of objects
568
- return v4.z.array(naturalZodSchema(itemType));
569
- }
570
- // Handle enum arrays
571
- return v4.z.enum(definition);
572
- }
573
- }
574
- else {
575
- // Handle enum arrays
576
- return v4.z.enum(definition);
577
- }
578
- }
579
- else if (definition && typeof definition === "object") {
580
- if (Object.keys(definition).length === 0) {
581
- // Handle empty object - accept any key-value pairs
582
- return v4.z.record(v4.z.string(), v4.z.any());
583
- }
584
- else {
585
- // Handle object with properties
586
- const schemaShape = {};
587
- for (const [key, value] of Object.entries(definition)) {
588
- schemaShape[key] = naturalZodSchema(value);
589
- }
590
- return v4.z.object(schemaShape);
591
- }
592
- }
593
- else {
594
- switch (definition) {
595
- case String:
596
- return v4.z.string();
597
- case Number:
598
- return v4.z.number();
599
- case Boolean:
600
- return v4.z.boolean();
601
- case Object:
602
- return v4.z.record(v4.z.string(), v4.z.any());
603
- case Array:
604
- return v4.z.array(v4.z.any());
605
- default:
606
- throw new Error(`Unsupported type: ${definition}`);
607
- }
608
- }
609
- }
610
-
611
853
  //
612
854
  // Constants
613
855
  //
@@ -807,26 +1049,242 @@ var ErrorCategory;
807
1049
  ErrorCategory["Unknown"] = "unknown";
808
1050
  })(ErrorCategory || (ErrorCategory = {}));
809
1051
 
1052
+ /**
1053
+ * Transient network error detection utility.
1054
+ *
1055
+ * Detects low-level Node.js/undici network errors that indicate
1056
+ * a temporary network issue (not a provider API error).
1057
+ * These errors should always be retried.
1058
+ */
810
1059
  //
811
1060
  //
812
1061
  // Constants
813
1062
  //
814
- const STRUCTURED_OUTPUT_TOOL_NAME$2 = "structured_output";
815
- // Regular expression to parse data URLs: data:mime/type;base64,data
816
- const DATA_URL_REGEX = /^data:([^;]+);base64,(.+)$/;
817
- /**
818
- * Parse a data URL into its components
819
- */
820
- function parseDataUrl(dataUrl) {
821
- const match = dataUrl.match(DATA_URL_REGEX);
822
- if (!match)
823
- return null;
824
- return { mediaType: match[1], data: match[2] };
825
- }
1063
+ /** Error codes from Node.js net/dns subsystems that indicate transient failures */
1064
+ const TRANSIENT_ERROR_CODES = new Set([
1065
+ "ECONNREFUSED",
1066
+ "ECONNRESET",
1067
+ "EAI_AGAIN",
1068
+ "ENETRESET",
1069
+ "ENETUNREACH",
1070
+ "ENOTFOUND",
1071
+ "EPIPE",
1072
+ "ETIMEDOUT",
1073
+ ]);
1074
+ /** Substrings in error messages that indicate transient network issues */
1075
+ const TRANSIENT_MESSAGE_PATTERNS = [
1076
+ "network",
1077
+ "socket hang up",
1078
+ "terminated",
1079
+ ];
1080
+ //
1081
+ //
1082
+ // Helpers
1083
+ //
826
1084
  /**
827
- * Convert standardized content items to Anthropic format
1085
+ * Check a single error (without walking the cause chain)
828
1086
  */
829
- function convertContentToAnthropic(content) {
1087
+ function matchesSingleError(error) {
1088
+ if (!(error instanceof Error))
1089
+ return false;
1090
+ // Check error code (e.g., ECONNRESET)
1091
+ const code = error.code;
1092
+ if (code && TRANSIENT_ERROR_CODES.has(code)) {
1093
+ return true;
1094
+ }
1095
+ // Check error message for transient patterns
1096
+ const message = error.message.toLowerCase();
1097
+ for (const pattern of TRANSIENT_MESSAGE_PATTERNS) {
1098
+ if (message.includes(pattern)) {
1099
+ return true;
1100
+ }
1101
+ }
1102
+ return false;
1103
+ }
1104
+ //
1105
+ //
1106
+ // Main
1107
+ //
1108
+ /**
1109
+ * Detect transient network errors by inspecting the error and its cause chain.
1110
+ *
1111
+ * Undici (Node.js fetch) wraps low-level errors like ECONNRESET inside
1112
+ * `TypeError: terminated`. This function recursively walks `error.cause`
1113
+ * to detect these wrapped errors.
1114
+ *
1115
+ * @param error - The error to inspect
1116
+ * @returns true if the error (or any cause in its chain) is a transient network error
1117
+ */
1118
+ function isTransientNetworkError(error) {
1119
+ let current = error;
1120
+ while (current) {
1121
+ if (matchesSingleError(current)) {
1122
+ return true;
1123
+ }
1124
+ // Walk the cause chain (cause is ES2022, cast for compatibility)
1125
+ const cause = current.cause;
1126
+ if (current instanceof Error && cause) {
1127
+ current = cause;
1128
+ }
1129
+ else {
1130
+ break;
1131
+ }
1132
+ }
1133
+ return false;
1134
+ }
1135
+
1136
+ //
1137
+ //
1138
+ // Constants
1139
+ //
1140
+ const STRUCTURED_OUTPUT_TOOL_NAME$3 = "structured_output";
1141
+ const STRUCTURED_OUTPUT_NON_PARSE_STOP_REASONS = new Set([
1142
+ "refusal",
1143
+ "max_tokens",
1144
+ ]);
1145
+ // Regular expression to parse data URLs: data:mime/type;base64,data
1146
+ const DATA_URL_REGEX$1 = /^data:([^;]+);base64,(.+)$/;
1147
+ // String formats accepted by Anthropic's structured-output grammar compiler.
1148
+ // Other formats are stripped (move to description) so the API does not 400.
1149
+ const SUPPORTED_STRING_FORMATS = new Set([
1150
+ "date",
1151
+ "date-time",
1152
+ "duration",
1153
+ "email",
1154
+ "hostname",
1155
+ "ipv4",
1156
+ "ipv6",
1157
+ "time",
1158
+ "uri",
1159
+ "uuid",
1160
+ ]);
1161
+ // Top-level keywords stripped wholesale before sending: not part of the spec
1162
+ // the API enforces and the validator can reject them.
1163
+ const STRIPPED_TOP_LEVEL_KEYWORDS = new Set(["$schema", "$id"]);
1164
+ // Keywords Anthropic's structured-output grammar does not support. They are
1165
+ // removed from the schema and appended to `description` so the model still
1166
+ // sees the intent.
1167
+ const UNSUPPORTED_CONSTRAINT_KEYWORDS = new Set([
1168
+ "exclusiveMaximum",
1169
+ "exclusiveMinimum",
1170
+ "maxItems",
1171
+ "maxLength",
1172
+ "maxProperties",
1173
+ "maximum",
1174
+ "minLength",
1175
+ "minProperties",
1176
+ "minimum",
1177
+ "multipleOf",
1178
+ "pattern",
1179
+ "patternProperties",
1180
+ "uniqueItems",
1181
+ ]);
1182
+ /**
1183
+ * Recursively transform a JSON Schema into the strict shape Anthropic's
1184
+ * structured-output grammar accepts: object types must have
1185
+ * `additionalProperties: false`, unsupported numeric/string/array
1186
+ * constraints are appended to `description`, and unsupported string
1187
+ * formats are stripped. Mirrors @anthropic-ai/sdk's `transformJSONSchema`
1188
+ * but inline so we do not take a runtime dependency on the optional
1189
+ * peer SDK.
1190
+ */
1191
+ function sanitizeJsonSchemaForAnthropic(schema, isRoot = true) {
1192
+ const result = {};
1193
+ const carriedConstraints = [];
1194
+ for (const [key, value] of Object.entries(schema)) {
1195
+ if (isRoot && STRIPPED_TOP_LEVEL_KEYWORDS.has(key)) {
1196
+ continue;
1197
+ }
1198
+ if (UNSUPPORTED_CONSTRAINT_KEYWORDS.has(key)) {
1199
+ carriedConstraints.push(`${key}: ${JSON.stringify(value)}`);
1200
+ continue;
1201
+ }
1202
+ if (key === "format" && typeof value === "string") {
1203
+ if (SUPPORTED_STRING_FORMATS.has(value)) {
1204
+ result[key] = value;
1205
+ }
1206
+ else {
1207
+ carriedConstraints.push(`format: ${JSON.stringify(value)}`);
1208
+ }
1209
+ continue;
1210
+ }
1211
+ if (key === "minItems" && typeof value === "number") {
1212
+ if (value === 0 || value === 1) {
1213
+ result[key] = value;
1214
+ }
1215
+ else {
1216
+ carriedConstraints.push(`minItems: ${value}`);
1217
+ }
1218
+ continue;
1219
+ }
1220
+ if (key === "properties" &&
1221
+ value &&
1222
+ typeof value === "object" &&
1223
+ !Array.isArray(value)) {
1224
+ const transformedProps = {};
1225
+ for (const [propName, propSchema] of Object.entries(value)) {
1226
+ transformedProps[propName] = isJsonSchema(propSchema)
1227
+ ? sanitizeJsonSchemaForAnthropic(propSchema, false)
1228
+ : propSchema;
1229
+ }
1230
+ result[key] = transformedProps;
1231
+ continue;
1232
+ }
1233
+ if ((key === "items" || key === "additionalItems" || key === "contains") &&
1234
+ isJsonSchema(value)) {
1235
+ result[key] = sanitizeJsonSchemaForAnthropic(value, false);
1236
+ continue;
1237
+ }
1238
+ if ((key === "anyOf" || key === "oneOf" || key === "allOf") &&
1239
+ Array.isArray(value)) {
1240
+ const targetKey = key === "oneOf" ? "anyOf" : key;
1241
+ result[targetKey] = value.map((entry) => isJsonSchema(entry)
1242
+ ? sanitizeJsonSchemaForAnthropic(entry, false)
1243
+ : entry);
1244
+ continue;
1245
+ }
1246
+ if (key === "$defs" && value && typeof value === "object") {
1247
+ const transformedDefs = {};
1248
+ for (const [defName, defSchema] of Object.entries(value)) {
1249
+ transformedDefs[defName] = isJsonSchema(defSchema)
1250
+ ? sanitizeJsonSchemaForAnthropic(defSchema, false)
1251
+ : defSchema;
1252
+ }
1253
+ result[key] = transformedDefs;
1254
+ continue;
1255
+ }
1256
+ if (key === "additionalProperties") {
1257
+ // Always force `false` on objects below; ignore caller-supplied value.
1258
+ continue;
1259
+ }
1260
+ result[key] = value;
1261
+ }
1262
+ if (result.type === "object") {
1263
+ result.additionalProperties = false;
1264
+ }
1265
+ if (carriedConstraints.length > 0) {
1266
+ const existing = typeof result.description === "string" ? result.description : "";
1267
+ const suffix = `{${carriedConstraints.join(", ")}}`;
1268
+ result.description = existing ? `${existing}\n\n${suffix}` : suffix;
1269
+ }
1270
+ return result;
1271
+ }
1272
+ function isJsonSchema(value) {
1273
+ return typeof value === "object" && value !== null && !Array.isArray(value);
1274
+ }
1275
+ /**
1276
+ * Parse a data URL into its components
1277
+ */
1278
+ function parseDataUrl(dataUrl) {
1279
+ const match = dataUrl.match(DATA_URL_REGEX$1);
1280
+ if (!match)
1281
+ return null;
1282
+ return { mediaType: match[1], data: match[2] };
1283
+ }
1284
+ /**
1285
+ * Convert standardized content items to Anthropic format
1286
+ */
1287
+ function convertContentToAnthropic(content) {
830
1288
  if (typeof content === "string") {
831
1289
  return content;
832
1290
  }
@@ -891,6 +1349,47 @@ const NOT_RETRYABLE_ERROR_NAMES = [
891
1349
  "NotFoundError",
892
1350
  "PermissionDeniedError",
893
1351
  ];
1352
+ // Models known not to accept `temperature`.
1353
+ // Patterns (not exact names) so dated variants and future releases are covered
1354
+ // without code changes — Anthropic is trending toward removing temperature on
1355
+ // newer Claude models.
1356
+ const MODELS_WITHOUT_TEMPERATURE$2 = [
1357
+ /^claude-opus-4-[789]/,
1358
+ /^claude-opus-[5-9]/,
1359
+ ];
1360
+ function isTemperatureDeprecationError$3(error) {
1361
+ if (!error || typeof error !== "object")
1362
+ return false;
1363
+ const err = error;
1364
+ const name = error?.constructor?.name;
1365
+ if (name !== "BadRequestError" && err.status !== 400)
1366
+ return false;
1367
+ const messages = [err.message, err.error?.message].filter((m) => typeof m === "string");
1368
+ return messages.some((m) => m.toLowerCase().includes("temperature"));
1369
+ }
1370
+ /**
1371
+ * Detect 400 errors that indicate the model itself does not support native
1372
+ * structured outputs (`output_config.format`). Citations + structured output
1373
+ * is also a 400 case but is a caller error rather than a model-capability
1374
+ * gap, so we explicitly skip it to avoid masking the real problem under a
1375
+ * tool-emulation retry. The deprecated-`output_format` 400 (API renamed the
1376
+ * field) is also explicitly excluded — that's a code-path bug, not a model
1377
+ * gap; it should propagate so we notice and fix it.
1378
+ */
1379
+ function isStructuredOutputUnsupportedError$1(error) {
1380
+ if (!error || typeof error !== "object")
1381
+ return false;
1382
+ const err = error;
1383
+ const name = error?.constructor?.name;
1384
+ if (name !== "BadRequestError" && err.status !== 400)
1385
+ return false;
1386
+ const messages = [err.message, err.error?.message].filter((m) => typeof m === "string");
1387
+ if (messages.some((m) => /citation/i.test(m)))
1388
+ return false;
1389
+ if (messages.some((m) => /deprecated/i.test(m)))
1390
+ return false;
1391
+ return messages.some((m) => /output_config|output_format|json[_ ]schema|structured/i.test(m));
1392
+ }
894
1393
  //
895
1394
  //
896
1395
  // Main
@@ -905,6 +1404,33 @@ class AnthropicAdapter extends BaseProviderAdapter {
905
1404
  super(...arguments);
906
1405
  this.name = PROVIDER.ANTHROPIC.NAME;
907
1406
  this.defaultModel = PROVIDER.ANTHROPIC.MODEL.DEFAULT;
1407
+ // Session-level cache of models observed to reject `temperature` at runtime.
1408
+ // Populated by executeRequest on 400 errors so repeat calls skip the param.
1409
+ this.runtimeNoTemperatureModels = new Set();
1410
+ // Session-level cache of models observed to reject `output_format`. When a
1411
+ // model is in this set, buildRequest engages the legacy fake-tool path
1412
+ // instead of native structured output.
1413
+ this.runtimeNoStructuredOutputModels = new Set();
1414
+ }
1415
+ rememberModelRejectsTemperature(model) {
1416
+ this.runtimeNoTemperatureModels.add(model);
1417
+ }
1418
+ clearRuntimeNoTemperatureModels() {
1419
+ this.runtimeNoTemperatureModels.clear();
1420
+ }
1421
+ rememberModelRejectsStructuredOutput(model) {
1422
+ this.runtimeNoStructuredOutputModels.add(model);
1423
+ }
1424
+ clearRuntimeNoStructuredOutputModels() {
1425
+ this.runtimeNoStructuredOutputModels.clear();
1426
+ }
1427
+ supportsTemperature(model) {
1428
+ if (this.runtimeNoTemperatureModels.has(model))
1429
+ return false;
1430
+ return !MODELS_WITHOUT_TEMPERATURE$2.some((pattern) => pattern.test(model));
1431
+ }
1432
+ supportsStructuredOutput(model) {
1433
+ return !this.runtimeNoStructuredOutputModels.has(model);
908
1434
  }
909
1435
  //
910
1436
  // Request Building
@@ -973,8 +1499,22 @@ class AnthropicAdapter extends BaseProviderAdapter {
973
1499
  if (request.system) {
974
1500
  anthropicRequest.system = request.system;
975
1501
  }
976
- if (request.tools && request.tools.length > 0) {
977
- anthropicRequest.tools = request.tools.map((tool) => ({
1502
+ const useFallbackStructuredOutput = Boolean(request.format) &&
1503
+ !this.supportsStructuredOutput(anthropicRequest.model);
1504
+ const allTools = request.tools
1505
+ ? [...request.tools]
1506
+ : [];
1507
+ if (useFallbackStructuredOutput && request.format) {
1508
+ log$1.warn(`[AnthropicAdapter] Using legacy structured_output tool fallback for model ${anthropicRequest.model}; native output_config previously rejected for this model.`);
1509
+ allTools.push({
1510
+ name: STRUCTURED_OUTPUT_TOOL_NAME$3,
1511
+ description: "Output a structured JSON object, " +
1512
+ "use this before your final response to give structured outputs to the user",
1513
+ parameters: request.format,
1514
+ });
1515
+ }
1516
+ if (allTools.length > 0) {
1517
+ anthropicRequest.tools = allTools.map((tool) => ({
978
1518
  name: tool.name,
979
1519
  description: tool.description,
980
1520
  input_schema: {
@@ -983,10 +1523,19 @@ class AnthropicAdapter extends BaseProviderAdapter {
983
1523
  },
984
1524
  type: "custom",
985
1525
  }));
986
- // Determine tool choice based on whether structured output is requested
987
- const hasStructuredOutput = request.tools.some((t) => t.name === STRUCTURED_OUTPUT_TOOL_NAME$2);
988
- anthropicRequest.tool_choice = {
989
- type: hasStructuredOutput ? "any" : "auto",
1526
+ anthropicRequest.tool_choice = useFallbackStructuredOutput
1527
+ ? { type: "any" }
1528
+ : { type: "auto" };
1529
+ }
1530
+ // Native structured output: send schema as `output_config.format`. The
1531
+ // legacy tool-emulation path is engaged only as a runtime fallback for
1532
+ // models the API has flagged as not supporting native structured output.
1533
+ if (request.format && !useFallbackStructuredOutput) {
1534
+ anthropicRequest.output_config = {
1535
+ format: {
1536
+ type: "json_schema",
1537
+ schema: request.format,
1538
+ },
990
1539
  };
991
1540
  }
992
1541
  if (request.providerOptions) {
@@ -996,10 +1545,20 @@ class AnthropicAdapter extends BaseProviderAdapter {
996
1545
  if (request.temperature !== undefined) {
997
1546
  anthropicRequest.temperature = request.temperature;
998
1547
  }
1548
+ // Strip temperature for models that don't support it (denylist + runtime cache)
1549
+ if (anthropicRequest.temperature !== undefined &&
1550
+ !this.supportsTemperature(anthropicRequest.model)) {
1551
+ delete anthropicRequest.temperature;
1552
+ }
999
1553
  return anthropicRequest;
1000
1554
  }
1001
- formatTools(toolkit, outputSchema) {
1002
- const tools = toolkit.tools.map((tool) => ({
1555
+ formatTools(toolkit,
1556
+ // outputSchema is part of the interface contract but Anthropic now uses
1557
+ // native `output_format` (set in buildRequest), so we no longer inject a
1558
+ // synthetic structured-output tool here.
1559
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
1560
+ _outputSchema) {
1561
+ return toolkit.tools.map((tool) => ({
1003
1562
  name: tool.name,
1004
1563
  description: tool.description,
1005
1564
  parameters: {
@@ -1007,16 +1566,6 @@ class AnthropicAdapter extends BaseProviderAdapter {
1007
1566
  type: "object",
1008
1567
  },
1009
1568
  }));
1010
- // Add structured output tool if schema is provided
1011
- if (outputSchema) {
1012
- tools.push({
1013
- name: STRUCTURED_OUTPUT_TOOL_NAME$2,
1014
- description: "Output a structured JSON object, " +
1015
- "use this before your final response to give structured outputs to the user",
1016
- parameters: outputSchema,
1017
- });
1018
- }
1019
- return tools;
1020
1569
  }
1021
1570
  formatOutputSchema(schema) {
1022
1571
  let jsonSchema;
@@ -1035,26 +1584,100 @@ class AnthropicAdapter extends BaseProviderAdapter {
1035
1584
  : naturalZodSchema(schema);
1036
1585
  jsonSchema = v4.z.toJSONSchema(zodSchema);
1037
1586
  }
1038
- // Remove $schema property (causes issues with validator)
1039
- if (jsonSchema.$schema) {
1040
- delete jsonSchema.$schema;
1041
- }
1042
- return jsonSchema;
1587
+ return sanitizeJsonSchemaForAnthropic(jsonSchema);
1043
1588
  }
1044
1589
  //
1045
1590
  // API Execution
1046
1591
  //
1047
- async executeRequest(client, request) {
1592
+ async executeRequest(client, request, signal) {
1048
1593
  const anthropic = client;
1049
- return (await anthropic.messages.create(request));
1594
+ const anthropicRequest = request;
1595
+ const wantsStructuredOutput = Boolean(anthropicRequest.output_config);
1596
+ try {
1597
+ const response = (await anthropic.messages.create(anthropicRequest, signal ? { signal } : undefined));
1598
+ if (wantsStructuredOutput) {
1599
+ response.__jaypieStructuredOutput = true;
1600
+ }
1601
+ return response;
1602
+ }
1603
+ catch (error) {
1604
+ if (signal?.aborted)
1605
+ return undefined;
1606
+ // If the model rejected `temperature`, cache it and retry without the param
1607
+ if (anthropicRequest.temperature !== undefined &&
1608
+ isTemperatureDeprecationError$3(error)) {
1609
+ this.rememberModelRejectsTemperature(anthropicRequest.model);
1610
+ const retryRequest = { ...anthropicRequest };
1611
+ delete retryRequest.temperature;
1612
+ const response = (await anthropic.messages.create(retryRequest, signal ? { signal } : undefined));
1613
+ if (wantsStructuredOutput) {
1614
+ response.__jaypieStructuredOutput = true;
1615
+ }
1616
+ return response;
1617
+ }
1618
+ // If the model rejected native structured output, cache it and retry
1619
+ // via the legacy fake-tool emulation path.
1620
+ if (wantsStructuredOutput && isStructuredOutputUnsupportedError$1(error)) {
1621
+ const model = anthropicRequest.model;
1622
+ this.rememberModelRejectsStructuredOutput(model);
1623
+ log$1.warn(`[AnthropicAdapter] Model ${model} rejected native output_config; falling back to legacy structured_output tool emulation.`);
1624
+ const fallbackRequest = this.toFallbackStructuredOutputRequest(anthropicRequest);
1625
+ return (await anthropic.messages.create(fallbackRequest, signal ? { signal } : undefined));
1626
+ }
1627
+ throw error;
1628
+ }
1629
+ }
1630
+ /**
1631
+ * Rebuild a structured-output request without `output_format`, swapping in
1632
+ * the legacy fake-tool emulation. Used as a runtime fallback when a model
1633
+ * rejects native `output_config.format`.
1634
+ */
1635
+ toFallbackStructuredOutputRequest(request) {
1636
+ const { output_config, ...rest } = request;
1637
+ if (!output_config)
1638
+ return request;
1639
+ const fallbackRequest = { ...rest };
1640
+ const fakeTool = {
1641
+ name: STRUCTURED_OUTPUT_TOOL_NAME$3,
1642
+ description: "Output a structured JSON object, " +
1643
+ "use this before your final response to give structured outputs to the user",
1644
+ input_schema: {
1645
+ ...output_config.format.schema,
1646
+ type: "object",
1647
+ },
1648
+ type: "custom",
1649
+ };
1650
+ fallbackRequest.tools = [...(fallbackRequest.tools ?? []), fakeTool];
1651
+ fallbackRequest.tool_choice = { type: "any" };
1652
+ return fallbackRequest;
1050
1653
  }
1051
- async *executeStreamRequest(client, request) {
1654
+ async *executeStreamRequest(client, request, signal) {
1052
1655
  const anthropic = client;
1053
- const streamRequest = {
1656
+ // Preserve `output_config` when passing through to the SDK by typing
1657
+ // through the local extension instead of the upstream
1658
+ // MessageCreateParams shape.
1659
+ let streamRequest = {
1054
1660
  ...request,
1055
1661
  stream: true,
1056
1662
  };
1057
- const stream = await anthropic.messages.create(streamRequest);
1663
+ let stream;
1664
+ try {
1665
+ stream = await anthropic.messages.create(streamRequest, signal ? { signal } : undefined);
1666
+ }
1667
+ catch (error) {
1668
+ if (streamRequest.temperature !== undefined &&
1669
+ isTemperatureDeprecationError$3(error)) {
1670
+ this.rememberModelRejectsTemperature(streamRequest.model);
1671
+ streamRequest = {
1672
+ ...streamRequest,
1673
+ };
1674
+ delete streamRequest.temperature;
1675
+ stream = await anthropic.messages.create(streamRequest, signal ? { signal } : undefined);
1676
+ }
1677
+ else {
1678
+ throw error;
1679
+ }
1680
+ }
1058
1681
  // Track current tool call being built
1059
1682
  let currentToolCall = null;
1060
1683
  // Track usage for final chunk
@@ -1141,43 +1764,659 @@ class AnthropicAdapter extends BaseProviderAdapter {
1141
1764
  //
1142
1765
  // Response Parsing
1143
1766
  //
1144
- parseResponse(response, _options) {
1145
- const anthropicResponse = response;
1146
- const content = this.extractContent(anthropicResponse);
1147
- const hasToolCalls = anthropicResponse.stop_reason === "tool_use";
1767
+ parseResponse(response, _options) {
1768
+ const anthropicResponse = response;
1769
+ const content = this.extractContent(anthropicResponse);
1770
+ const hasToolCalls = anthropicResponse.stop_reason === "tool_use";
1771
+ return {
1772
+ content,
1773
+ hasToolCalls,
1774
+ stopReason: anthropicResponse.stop_reason ?? undefined,
1775
+ usage: this.extractUsage(anthropicResponse, anthropicResponse.model),
1776
+ raw: anthropicResponse,
1777
+ };
1778
+ }
1779
+ extractToolCalls(response) {
1780
+ const anthropicResponse = response;
1781
+ const toolCalls = [];
1782
+ for (const block of anthropicResponse.content) {
1783
+ if (block.type === "tool_use") {
1784
+ toolCalls.push({
1785
+ callId: block.id,
1786
+ name: block.name,
1787
+ arguments: JSON.stringify(block.input),
1788
+ raw: block,
1789
+ });
1790
+ }
1791
+ }
1792
+ return toolCalls;
1793
+ }
1794
+ extractUsage(response, model) {
1795
+ const anthropicResponse = response;
1796
+ // Check for thinking tokens in the usage (extended thinking feature)
1797
+ // Anthropic includes thinking tokens in a separate field when enabled
1798
+ const usage = anthropicResponse.usage;
1799
+ return {
1800
+ input: usage.input_tokens,
1801
+ output: usage.output_tokens,
1802
+ reasoning: usage.thinking_tokens || 0,
1803
+ total: usage.input_tokens + usage.output_tokens,
1804
+ provider: this.name,
1805
+ model,
1806
+ };
1807
+ }
1808
+ //
1809
+ // Tool Result Handling
1810
+ //
1811
+ formatToolResult(toolCall, result) {
1812
+ return {
1813
+ type: "tool_result",
1814
+ tool_use_id: toolCall.callId,
1815
+ content: result.output,
1816
+ };
1817
+ }
1818
+ appendToolResult(request, toolCall, result) {
1819
+ const anthropicRequest = request;
1820
+ const toolCallRaw = toolCall.raw;
1821
+ // Add assistant message with the tool use
1822
+ anthropicRequest.messages.push({
1823
+ role: "assistant",
1824
+ content: [toolCallRaw],
1825
+ });
1826
+ // Add user message with the tool result
1827
+ anthropicRequest.messages.push({
1828
+ role: "user",
1829
+ content: [this.formatToolResult(toolCall, result)],
1830
+ });
1831
+ return anthropicRequest;
1832
+ }
1833
+ //
1834
+ // History Management
1835
+ //
1836
+ responseToHistoryItems(response) {
1837
+ const anthropicResponse = response;
1838
+ const historyItems = [];
1839
+ // Check if this is a tool use response
1840
+ if (anthropicResponse.stop_reason === "tool_use") {
1841
+ // Don't add to history yet - will be added after tool execution
1842
+ return historyItems;
1843
+ }
1844
+ // Include thinking blocks for extended thinking support
1845
+ // Thinking blocks are preserved in history so extractReasoning can find them
1846
+ for (const block of anthropicResponse.content) {
1847
+ if (block.type === "thinking") {
1848
+ // Push raw block - types are loosely checked at runtime
1849
+ // This allows extractReasoning to access the thinking property
1850
+ historyItems.push(block);
1851
+ }
1852
+ }
1853
+ // Extract text content for non-tool responses
1854
+ const textBlock = anthropicResponse.content.find((block) => block.type === "text");
1855
+ if (textBlock) {
1856
+ historyItems.push({
1857
+ content: textBlock.text,
1858
+ role: PROVIDER.ANTHROPIC.ROLE.ASSISTANT,
1859
+ type: exports.LlmMessageType.Message,
1860
+ });
1861
+ }
1862
+ return historyItems;
1863
+ }
1864
+ //
1865
+ // Error Classification
1866
+ //
1867
+ classifyError(error) {
1868
+ const errorName = error?.constructor?.name;
1869
+ // Check for rate limit error
1870
+ if (errorName === "RateLimitError") {
1871
+ return {
1872
+ error,
1873
+ category: ErrorCategory.RateLimit,
1874
+ shouldRetry: false,
1875
+ suggestedDelayMs: 60000,
1876
+ };
1877
+ }
1878
+ // Check for retryable errors
1879
+ if (RETRYABLE_ERROR_NAMES.includes(errorName)) {
1880
+ return {
1881
+ error,
1882
+ category: ErrorCategory.Retryable,
1883
+ shouldRetry: true,
1884
+ };
1885
+ }
1886
+ // Check for non-retryable errors
1887
+ if (NOT_RETRYABLE_ERROR_NAMES.includes(errorName)) {
1888
+ return {
1889
+ error,
1890
+ category: ErrorCategory.Unrecoverable,
1891
+ shouldRetry: false,
1892
+ };
1893
+ }
1894
+ // Check for transient network errors (ECONNRESET, etc.)
1895
+ if (isTransientNetworkError(error)) {
1896
+ return {
1897
+ error,
1898
+ category: ErrorCategory.Retryable,
1899
+ shouldRetry: true,
1900
+ };
1901
+ }
1902
+ // Unknown error - treat as potentially retryable
1903
+ return {
1904
+ error,
1905
+ category: ErrorCategory.Unknown,
1906
+ shouldRetry: true,
1907
+ };
1908
+ }
1909
+ //
1910
+ // Provider-Specific Features
1911
+ //
1912
+ isComplete(response) {
1913
+ const anthropicResponse = response;
1914
+ return anthropicResponse.stop_reason !== "tool_use";
1915
+ }
1916
+ hasStructuredOutput(response) {
1917
+ const anthropicResponse = response;
1918
+ // Native path: executeRequest annotates the response when we sent
1919
+ // `output_format`, so we can detect intent statelessly.
1920
+ if (anthropicResponse.__jaypieStructuredOutput) {
1921
+ return this.extractStructuredOutput(response) !== undefined;
1922
+ }
1923
+ // Fallback path: legacy fake-tool emulation, kept for models that the
1924
+ // runtime has cached as not supporting `output_format`.
1925
+ const lastBlock = anthropicResponse.content[anthropicResponse.content.length - 1];
1926
+ return (lastBlock?.type === "tool_use" &&
1927
+ lastBlock.name === STRUCTURED_OUTPUT_TOOL_NAME$3);
1928
+ }
1929
+ extractStructuredOutput(response) {
1930
+ const anthropicResponse = response;
1931
+ if (anthropicResponse.__jaypieStructuredOutput) {
1932
+ // Refusal and truncation are explicit non-JSON outcomes per Anthropic
1933
+ // structured-outputs docs — surface the text upstream instead of
1934
+ // forcing a JSON.parse on what is not JSON.
1935
+ if (anthropicResponse.stop_reason &&
1936
+ STRUCTURED_OUTPUT_NON_PARSE_STOP_REASONS.has(anthropicResponse.stop_reason)) {
1937
+ return undefined;
1938
+ }
1939
+ const textBlock = anthropicResponse.content.find((block) => block.type === "text");
1940
+ if (!textBlock)
1941
+ return undefined;
1942
+ try {
1943
+ const parsed = JSON.parse(textBlock.text);
1944
+ return parsed;
1945
+ }
1946
+ catch {
1947
+ return undefined;
1948
+ }
1949
+ }
1950
+ // Fallback path: legacy fake-tool emulation
1951
+ const lastBlock = anthropicResponse.content[anthropicResponse.content.length - 1];
1952
+ if (lastBlock?.type === "tool_use" &&
1953
+ lastBlock.name === STRUCTURED_OUTPUT_TOOL_NAME$3) {
1954
+ return lastBlock.input;
1955
+ }
1956
+ return undefined;
1957
+ }
1958
+ //
1959
+ // Private Helpers
1960
+ //
1961
+ extractContent(response) {
1962
+ // Check for structured output first
1963
+ if (this.hasStructuredOutput(response)) {
1964
+ return this.extractStructuredOutput(response);
1965
+ }
1966
+ // Extract text content
1967
+ const textBlock = response.content.find((block) => block.type === "text");
1968
+ return textBlock?.text;
1969
+ }
1970
+ }
1971
+ // Export singleton instance
1972
+ const anthropicAdapter = new AnthropicAdapter();
1973
+
1974
+ //
1975
+ //
1976
+ // Helpers
1977
+ //
1978
+ // Regular expression to parse data URLs
1979
+ const DATA_URL_REGEX = /^data:([^;]+);base64,(.+)$/;
1980
+ const MIME_TO_DOCUMENT_FORMAT = {
1981
+ "application/pdf": "pdf",
1982
+ "text/csv": "csv",
1983
+ "application/msword": "doc",
1984
+ "application/vnd.openxmlformats-officedocument.wordprocessingml.document": "docx",
1985
+ "application/vnd.ms-excel": "xls",
1986
+ "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet": "xlsx",
1987
+ "text/html": "html",
1988
+ "text/plain": "txt",
1989
+ "text/markdown": "md",
1990
+ };
1991
+ function convertContentToBedrock(content) {
1992
+ if (typeof content === "string") {
1993
+ return [{ text: content }];
1994
+ }
1995
+ return content.map((item) => {
1996
+ if (item.type === exports.LlmMessageType.InputText) {
1997
+ return { text: item.text };
1998
+ }
1999
+ if (item.type === exports.LlmMessageType.InputImage) {
2000
+ const imageUrl = item.image_url || "";
2001
+ const match = imageUrl.match(DATA_URL_REGEX);
2002
+ if (match) {
2003
+ const format = match[1].split("/")[1] || "jpeg";
2004
+ const bytes = Buffer.from(match[2], "base64");
2005
+ return {
2006
+ image: {
2007
+ format,
2008
+ source: { bytes: new Uint8Array(bytes) },
2009
+ },
2010
+ };
2011
+ }
2012
+ return { text: `[Image: ${imageUrl}]` };
2013
+ }
2014
+ if (item.type === exports.LlmMessageType.InputFile) {
2015
+ const fileData = typeof item.file_data === "string" ? item.file_data : "";
2016
+ const match = fileData.match(DATA_URL_REGEX);
2017
+ if (match) {
2018
+ const mimeType = match[1];
2019
+ const documentFormat = MIME_TO_DOCUMENT_FORMAT[mimeType];
2020
+ if (documentFormat) {
2021
+ const bytes = Buffer.from(match[2], "base64");
2022
+ const rawName = item.filename || "document";
2023
+ const name = rawName.replace(/[^a-zA-Z0-9 \-()[\]]/g, "_");
2024
+ return {
2025
+ document: {
2026
+ format: documentFormat,
2027
+ name,
2028
+ source: { bytes: new Uint8Array(bytes) },
2029
+ },
2030
+ };
2031
+ }
2032
+ }
2033
+ return { text: `[File: ${item.filename || "unknown"}]` };
2034
+ }
2035
+ return { text: JSON.stringify(item) };
2036
+ });
2037
+ }
2038
+ //
2039
+ //
2040
+ // Constants / helpers
2041
+ //
2042
+ const STRUCTURED_OUTPUT_TOOL_NAME$2 = "structured_output";
2043
+ function isOutputConfigUnsupportedError(error) {
2044
+ const msg = error?.message ?? "";
2045
+ return /outputConfig|output_config/i.test(msg);
2046
+ }
2047
+ function isTemperatureDeprecationError$2(error) {
2048
+ const msg = error?.message ?? "";
2049
+ return /temperature.*deprecated|deprecated.*temperature/i.test(msg);
2050
+ }
2051
+ function extractJson(text) {
2052
+ // Try direct parse first
2053
+ try {
2054
+ const parsed = JSON.parse(text);
2055
+ if (typeof parsed === "object" && parsed !== null)
2056
+ return parsed;
2057
+ }
2058
+ catch {
2059
+ // fall through
2060
+ }
2061
+ // Try stripping markdown code fences
2062
+ const fenceMatch = text.match(/```(?:json)?\s*([\s\S]*?)```/);
2063
+ if (fenceMatch) {
2064
+ try {
2065
+ const parsed = JSON.parse(fenceMatch[1].trim());
2066
+ if (typeof parsed === "object" && parsed !== null)
2067
+ return parsed;
2068
+ }
2069
+ catch {
2070
+ // fall through
2071
+ }
2072
+ }
2073
+ return undefined;
2074
+ }
2075
+ //
2076
+ //
2077
+ // Main
2078
+ //
2079
+ class BedrockAdapter extends BaseProviderAdapter {
2080
+ constructor() {
2081
+ super(...arguments);
2082
+ this.name = PROVIDER.BEDROCK.NAME;
2083
+ this.defaultModel = PROVIDER.BEDROCK.MODEL.DEFAULT;
2084
+ this._modelsFallbackToStructuredOutputTool = new Set();
2085
+ this._modelsWithoutTemperature = new Set();
2086
+ }
2087
+ rememberModelRejectsOutputConfig(model) {
2088
+ this._modelsFallbackToStructuredOutputTool.add(model);
2089
+ }
2090
+ useFakeToolForStructuredOutput(model) {
2091
+ return this._modelsFallbackToStructuredOutputTool.has(model);
2092
+ }
2093
+ rememberModelRejectsTemperature(model) {
2094
+ this._modelsWithoutTemperature.add(model);
2095
+ }
2096
+ supportsTemperature(model) {
2097
+ return !this._modelsWithoutTemperature.has(model);
2098
+ }
2099
+ //
2100
+ // Request Building
2101
+ //
2102
+ buildRequest(request) {
2103
+ const messages = [];
2104
+ for (const msg of request.messages) {
2105
+ const typedMsg = msg;
2106
+ if (typedMsg.role === "system")
2107
+ continue;
2108
+ if (typedMsg.type === exports.LlmMessageType.FunctionCall) {
2109
+ let parsedInput;
2110
+ try {
2111
+ parsedInput = JSON.parse(typedMsg.arguments || "{}");
2112
+ }
2113
+ catch {
2114
+ parsedInput = {};
2115
+ }
2116
+ messages.push({
2117
+ role: "assistant",
2118
+ content: [
2119
+ {
2120
+ toolUse: {
2121
+ toolUseId: typedMsg.call_id || "",
2122
+ name: typedMsg.name || "",
2123
+ input: parsedInput,
2124
+ },
2125
+ },
2126
+ ],
2127
+ });
2128
+ continue;
2129
+ }
2130
+ if (typedMsg.type === exports.LlmMessageType.FunctionCallOutput) {
2131
+ messages.push({
2132
+ role: "user",
2133
+ content: [
2134
+ {
2135
+ toolResult: {
2136
+ toolUseId: typedMsg.call_id || "",
2137
+ content: [{ text: typedMsg.output || "" }],
2138
+ },
2139
+ },
2140
+ ],
2141
+ });
2142
+ continue;
2143
+ }
2144
+ if (typedMsg.role && typedMsg.content !== undefined) {
2145
+ messages.push({
2146
+ role: typedMsg.role,
2147
+ content: convertContentToBedrock(typedMsg.content),
2148
+ });
2149
+ }
2150
+ }
2151
+ const model = request.model || this.defaultModel;
2152
+ const bedrockRequest = {
2153
+ modelId: model,
2154
+ messages,
2155
+ inferenceConfig: {
2156
+ maxTokens: 4096,
2157
+ },
2158
+ };
2159
+ if (request.system) {
2160
+ bedrockRequest.system = [{ text: request.system }];
2161
+ }
2162
+ if (request.temperature !== undefined && this.supportsTemperature(model)) {
2163
+ bedrockRequest.inferenceConfig = {
2164
+ ...bedrockRequest.inferenceConfig,
2165
+ temperature: request.temperature,
2166
+ };
2167
+ }
2168
+ if (request.tools && request.tools.length > 0) {
2169
+ bedrockRequest.toolConfig = {
2170
+ tools: request.tools.map((tool) => ({
2171
+ toolSpec: {
2172
+ name: tool.name,
2173
+ description: tool.description,
2174
+ inputSchema: {
2175
+ json: tool.parameters,
2176
+ },
2177
+ },
2178
+ })),
2179
+ };
2180
+ }
2181
+ if (request.instructions && messages.length > 0) {
2182
+ const lastMsg = messages[messages.length - 1];
2183
+ if (lastMsg.content.length > 0) {
2184
+ const firstBlock = lastMsg.content[0];
2185
+ if ("text" in firstBlock) {
2186
+ firstBlock.text = firstBlock.text + "\n\n" + request.instructions;
2187
+ }
2188
+ }
2189
+ }
2190
+ if (request.format) {
2191
+ if (this.useFakeToolForStructuredOutput(model)) {
2192
+ const fakeTool = {
2193
+ toolSpec: {
2194
+ name: STRUCTURED_OUTPUT_TOOL_NAME$2,
2195
+ description: "REQUIRED: You MUST call this tool to provide your final response. " +
2196
+ "After gathering all necessary information (including results from other tools), " +
2197
+ "call this tool with the structured data to complete the request.",
2198
+ inputSchema: { json: request.format },
2199
+ },
2200
+ };
2201
+ bedrockRequest.toolConfig = {
2202
+ tools: [
2203
+ ...(bedrockRequest.toolConfig?.tools ?? []),
2204
+ fakeTool,
2205
+ ],
2206
+ };
2207
+ }
2208
+ else {
2209
+ bedrockRequest.outputConfig = {
2210
+ textFormat: {
2211
+ type: "json_schema",
2212
+ structure: {
2213
+ jsonSchema: {
2214
+ schema: JSON.stringify(request.format),
2215
+ name: "structured_output",
2216
+ },
2217
+ },
2218
+ },
2219
+ };
2220
+ }
2221
+ }
2222
+ if (request.providerOptions) {
2223
+ Object.assign(bedrockRequest, request.providerOptions);
2224
+ }
2225
+ return bedrockRequest;
2226
+ }
2227
+ formatTools(toolkit) {
2228
+ return toolkit.tools.map((tool) => ({
2229
+ name: tool.name,
2230
+ description: tool.description,
2231
+ parameters: {
2232
+ ...tool.parameters,
2233
+ type: "object",
2234
+ },
2235
+ }));
2236
+ }
2237
+ formatOutputSchema(schema) {
2238
+ const zodSchema = schema instanceof v4.z.ZodType
2239
+ ? schema
2240
+ : naturalZodSchema(schema);
2241
+ return v4.z.toJSONSchema(zodSchema);
2242
+ }
2243
+ //
2244
+ // API Execution
2245
+ //
2246
+ async executeRequest(client, request, signal) {
2247
+ const bedrockClient = client;
2248
+ const bedrockRequest = request;
2249
+ const { ConverseCommand } = await import('@aws-sdk/client-bedrock-runtime');
2250
+ const wantsStructuredOutput = Boolean(bedrockRequest.outputConfig);
2251
+ try {
2252
+ const response = (await bedrockClient.send(new ConverseCommand(bedrockRequest), signal ? { abortSignal: signal } : undefined));
2253
+ if (wantsStructuredOutput)
2254
+ response.__jaypieStructuredOutput = true;
2255
+ return response;
2256
+ }
2257
+ catch (error) {
2258
+ if (bedrockRequest.inferenceConfig?.temperature !== undefined &&
2259
+ isTemperatureDeprecationError$2(error)) {
2260
+ this.rememberModelRejectsTemperature(bedrockRequest.modelId || this.defaultModel);
2261
+ const retryRequest = {
2262
+ ...bedrockRequest,
2263
+ inferenceConfig: { ...bedrockRequest.inferenceConfig },
2264
+ };
2265
+ delete retryRequest.inferenceConfig.temperature;
2266
+ const response = (await bedrockClient.send(new ConverseCommand(retryRequest), signal ? { abortSignal: signal } : undefined));
2267
+ if (wantsStructuredOutput)
2268
+ response.__jaypieStructuredOutput = true;
2269
+ return response;
2270
+ }
2271
+ if (wantsStructuredOutput && isOutputConfigUnsupportedError(error)) {
2272
+ const model = bedrockRequest.modelId || this.defaultModel;
2273
+ this.rememberModelRejectsOutputConfig(model);
2274
+ const fallbackRequest = this.toFallbackStructuredOutputRequest(bedrockRequest);
2275
+ return (await bedrockClient.send(new ConverseCommand(fallbackRequest), signal ? { abortSignal: signal } : undefined));
2276
+ }
2277
+ throw error;
2278
+ }
2279
+ }
2280
+ toFallbackStructuredOutputRequest(request) {
2281
+ const { outputConfig, ...rest } = request;
2282
+ if (!outputConfig?.textFormat?.structure)
2283
+ return request;
2284
+ let schema;
2285
+ try {
2286
+ schema = JSON.parse(outputConfig.textFormat.structure.jsonSchema?.schema ?? "{}");
2287
+ }
2288
+ catch {
2289
+ schema = {};
2290
+ }
2291
+ const fakeTool = {
2292
+ toolSpec: {
2293
+ name: STRUCTURED_OUTPUT_TOOL_NAME$2,
2294
+ description: "REQUIRED: You MUST call this tool to provide your final response. " +
2295
+ "After gathering all necessary information (including results from other tools), " +
2296
+ "call this tool with the structured data to complete the request.",
2297
+ inputSchema: { json: schema },
2298
+ },
2299
+ };
2300
+ return {
2301
+ ...rest,
2302
+ toolConfig: {
2303
+ tools: [...(rest.toolConfig?.tools ?? []), fakeTool],
2304
+ },
2305
+ };
2306
+ }
2307
+ async *executeStreamRequest(client, request, signal) {
2308
+ const bedrockClient = client;
2309
+ const bedrockRequest = request;
2310
+ const { ConverseStreamCommand } = await import('@aws-sdk/client-bedrock-runtime');
2311
+ const response = await bedrockClient.send(new ConverseStreamCommand(bedrockRequest), signal ? { abortSignal: signal } : undefined);
2312
+ if (!response.stream)
2313
+ return;
2314
+ let currentToolCall = null;
2315
+ let inputTokens = 0;
2316
+ let outputTokens = 0;
2317
+ const model = bedrockRequest.modelId || this.defaultModel;
2318
+ for await (const event of response.stream) {
2319
+ if (event.contentBlockStart?.start?.toolUse) {
2320
+ const toolUse = event.contentBlockStart.start.toolUse;
2321
+ currentToolCall = {
2322
+ toolUseId: toolUse.toolUseId || "",
2323
+ name: toolUse.name || "",
2324
+ arguments: "",
2325
+ };
2326
+ }
2327
+ else if (event.contentBlockDelta?.delta) {
2328
+ const delta = event.contentBlockDelta.delta;
2329
+ if (delta.text !== undefined) {
2330
+ yield { type: exports.LlmStreamChunkType.Text, content: delta.text };
2331
+ }
2332
+ else if (delta.toolUse?.input && currentToolCall) {
2333
+ currentToolCall.arguments += delta.toolUse.input;
2334
+ }
2335
+ }
2336
+ else if (event.contentBlockStop && currentToolCall) {
2337
+ yield {
2338
+ type: exports.LlmStreamChunkType.ToolCall,
2339
+ toolCall: {
2340
+ id: currentToolCall.toolUseId,
2341
+ name: currentToolCall.name,
2342
+ arguments: currentToolCall.arguments,
2343
+ },
2344
+ };
2345
+ currentToolCall = null;
2346
+ }
2347
+ else if (event.metadata?.usage) {
2348
+ inputTokens = event.metadata.usage.inputTokens ?? 0;
2349
+ outputTokens = event.metadata.usage.outputTokens ?? 0;
2350
+ }
2351
+ else if (event.messageStop) {
2352
+ yield {
2353
+ type: exports.LlmStreamChunkType.Done,
2354
+ usage: [
2355
+ {
2356
+ input: inputTokens,
2357
+ output: outputTokens,
2358
+ reasoning: 0,
2359
+ total: inputTokens + outputTokens,
2360
+ provider: this.name,
2361
+ model,
2362
+ },
2363
+ ],
2364
+ };
2365
+ }
2366
+ }
2367
+ }
2368
+ //
2369
+ // Response Parsing
2370
+ //
2371
+ parseResponse(response, options) {
2372
+ const bedrockResponse = response;
2373
+ const message = bedrockResponse.output?.message;
2374
+ const rawContent = this.extractContentFromMessage(message);
2375
+ let content = rawContent;
2376
+ if (options?.format && typeof rawContent === "string") {
2377
+ content = extractJson(rawContent) ?? rawContent;
2378
+ }
2379
+ // Don't surface structured_output fake tool as a real tool call
2380
+ const allToolUses = (bedrockResponse.output?.message?.content ?? []).filter((b) => "toolUse" in b);
2381
+ const hasOnlyStructuredOutputTool = allToolUses.length > 0 &&
2382
+ allToolUses.every((b) => b.toolUse.name === STRUCTURED_OUTPUT_TOOL_NAME$2);
2383
+ const hasToolCalls = bedrockResponse.stopReason === "tool_use" && !hasOnlyStructuredOutputTool;
1148
2384
  return {
1149
2385
  content,
1150
2386
  hasToolCalls,
1151
- stopReason: anthropicResponse.stop_reason ?? undefined,
1152
- usage: this.extractUsage(anthropicResponse, anthropicResponse.model),
1153
- raw: anthropicResponse,
2387
+ stopReason: bedrockResponse.stopReason ?? undefined,
2388
+ usage: this.extractUsage(bedrockResponse, bedrockResponse.modelId ||
2389
+ this.defaultModel),
2390
+ raw: bedrockResponse,
1154
2391
  };
1155
2392
  }
1156
2393
  extractToolCalls(response) {
1157
- const anthropicResponse = response;
2394
+ const bedrockResponse = response;
2395
+ const content = bedrockResponse.output?.message?.content ?? [];
1158
2396
  const toolCalls = [];
1159
- for (const block of anthropicResponse.content) {
1160
- if (block.type === "tool_use") {
2397
+ for (const block of content) {
2398
+ const typedBlock = block;
2399
+ if ("toolUse" in typedBlock && typedBlock.toolUse) {
2400
+ const toolUse = typedBlock.toolUse;
1161
2401
  toolCalls.push({
1162
- callId: block.id,
1163
- name: block.name,
1164
- arguments: JSON.stringify(block.input),
1165
- raw: block,
2402
+ callId: toolUse.toolUseId,
2403
+ name: toolUse.name,
2404
+ arguments: JSON.stringify(toolUse.input),
2405
+ raw: typedBlock,
1166
2406
  });
1167
2407
  }
1168
2408
  }
1169
2409
  return toolCalls;
1170
2410
  }
1171
2411
  extractUsage(response, model) {
1172
- const anthropicResponse = response;
1173
- // Check for thinking tokens in the usage (extended thinking feature)
1174
- // Anthropic includes thinking tokens in a separate field when enabled
1175
- const usage = anthropicResponse.usage;
2412
+ const bedrockResponse = response;
2413
+ const usage = bedrockResponse.usage;
1176
2414
  return {
1177
- input: usage.input_tokens,
1178
- output: usage.output_tokens,
1179
- reasoning: usage.thinking_tokens || 0,
1180
- total: usage.input_tokens + usage.output_tokens,
2415
+ input: usage?.inputTokens ?? 0,
2416
+ output: usage?.outputTokens ?? 0,
2417
+ reasoning: 0,
2418
+ total: usage?.totalTokens ??
2419
+ (usage?.inputTokens ?? 0) + (usage?.outputTokens ?? 0),
1181
2420
  provider: this.name,
1182
2421
  model,
1183
2422
  };
@@ -1187,52 +2426,40 @@ class AnthropicAdapter extends BaseProviderAdapter {
1187
2426
  //
1188
2427
  formatToolResult(toolCall, result) {
1189
2428
  return {
1190
- type: "tool_result",
1191
- tool_use_id: toolCall.callId,
1192
- content: result.output,
2429
+ toolResult: {
2430
+ toolUseId: toolCall.callId,
2431
+ content: [{ text: result.output }],
2432
+ },
1193
2433
  };
1194
2434
  }
1195
2435
  appendToolResult(request, toolCall, result) {
1196
- const anthropicRequest = request;
2436
+ const bedrockRequest = request;
1197
2437
  const toolCallRaw = toolCall.raw;
1198
- // Add assistant message with the tool use
1199
- anthropicRequest.messages.push({
2438
+ bedrockRequest.messages.push({
1200
2439
  role: "assistant",
1201
2440
  content: [toolCallRaw],
1202
2441
  });
1203
- // Add user message with the tool result
1204
- anthropicRequest.messages.push({
2442
+ bedrockRequest.messages.push({
1205
2443
  role: "user",
1206
2444
  content: [this.formatToolResult(toolCall, result)],
1207
2445
  });
1208
- return anthropicRequest;
2446
+ return bedrockRequest;
1209
2447
  }
1210
2448
  //
1211
2449
  // History Management
1212
2450
  //
1213
2451
  responseToHistoryItems(response) {
1214
- const anthropicResponse = response;
2452
+ const bedrockResponse = response;
1215
2453
  const historyItems = [];
1216
- // Check if this is a tool use response
1217
- if (anthropicResponse.stop_reason === "tool_use") {
1218
- // Don't add to history yet - will be added after tool execution
2454
+ if (bedrockResponse.stopReason === "tool_use") {
1219
2455
  return historyItems;
1220
2456
  }
1221
- // Include thinking blocks for extended thinking support
1222
- // Thinking blocks are preserved in history so extractReasoning can find them
1223
- for (const block of anthropicResponse.content) {
1224
- if (block.type === "thinking") {
1225
- // Push raw block - types are loosely checked at runtime
1226
- // This allows extractReasoning to access the thinking property
1227
- historyItems.push(block);
1228
- }
1229
- }
1230
- // Extract text content for non-tool responses
1231
- const textBlock = anthropicResponse.content.find((block) => block.type === "text");
2457
+ const content = bedrockResponse.output?.message?.content ?? [];
2458
+ const textBlock = content.find((block) => "text" in block);
1232
2459
  if (textBlock) {
1233
2460
  historyItems.push({
1234
2461
  content: textBlock.text,
1235
- role: PROVIDER.ANTHROPIC.ROLE.ASSISTANT,
2462
+ role: "assistant",
1236
2463
  type: exports.LlmMessageType.Message,
1237
2464
  });
1238
2465
  }
@@ -1243,8 +2470,10 @@ class AnthropicAdapter extends BaseProviderAdapter {
1243
2470
  //
1244
2471
  classifyError(error) {
1245
2472
  const errorName = error?.constructor?.name;
1246
- // Check for rate limit error
1247
- if (errorName === "RateLimitError") {
2473
+ const errorMessage = error?.message ?? "";
2474
+ if (errorName === "ThrottlingException" ||
2475
+ errorMessage.includes("ThrottlingException") ||
2476
+ errorMessage.includes("Too Many Requests")) {
1248
2477
  return {
1249
2478
  error,
1250
2479
  category: ErrorCategory.RateLimit,
@@ -1252,23 +2481,34 @@ class AnthropicAdapter extends BaseProviderAdapter {
1252
2481
  suggestedDelayMs: 60000,
1253
2482
  };
1254
2483
  }
1255
- // Check for retryable errors
1256
- if (RETRYABLE_ERROR_NAMES.includes(errorName)) {
2484
+ if (errorName === "ServiceUnavailableException" ||
2485
+ errorName === "InternalServerException" ||
2486
+ errorMessage.includes("ServiceUnavailableException") ||
2487
+ errorMessage.includes("InternalServerException")) {
1257
2488
  return {
1258
2489
  error,
1259
2490
  category: ErrorCategory.Retryable,
1260
2491
  shouldRetry: true,
1261
2492
  };
1262
2493
  }
1263
- // Check for non-retryable errors
1264
- if (NOT_RETRYABLE_ERROR_NAMES.includes(errorName)) {
2494
+ if (errorName === "AccessDeniedException" ||
2495
+ errorName === "ValidationException" ||
2496
+ errorName === "ResourceNotFoundException" ||
2497
+ errorMessage.includes("AccessDeniedException") ||
2498
+ errorMessage.includes("ValidationException")) {
1265
2499
  return {
1266
2500
  error,
1267
2501
  category: ErrorCategory.Unrecoverable,
1268
2502
  shouldRetry: false,
1269
2503
  };
1270
2504
  }
1271
- // Unknown error - treat as potentially retryable
2505
+ if (isTransientNetworkError(error)) {
2506
+ return {
2507
+ error,
2508
+ category: ErrorCategory.Retryable,
2509
+ shouldRetry: true,
2510
+ };
2511
+ }
1272
2512
  return {
1273
2513
  error,
1274
2514
  category: ErrorCategory.Unknown,
@@ -1276,49 +2516,87 @@ class AnthropicAdapter extends BaseProviderAdapter {
1276
2516
  };
1277
2517
  }
1278
2518
  //
1279
- // Provider-Specific Features
2519
+ // Structured Output
1280
2520
  //
1281
- isComplete(response) {
1282
- const anthropicResponse = response;
1283
- return anthropicResponse.stop_reason !== "tool_use";
1284
- }
1285
2521
  hasStructuredOutput(response) {
1286
- const anthropicResponse = response;
1287
- // Check if the last content block is a tool_use with structured_output
1288
- const lastBlock = anthropicResponse.content[anthropicResponse.content.length - 1];
1289
- return (lastBlock?.type === "tool_use" &&
1290
- lastBlock.name === STRUCTURED_OUTPUT_TOOL_NAME$2);
2522
+ const bedrockResponse = response;
2523
+ if (bedrockResponse.__jaypieStructuredOutput) {
2524
+ return this.extractStructuredOutput(response) !== undefined;
2525
+ }
2526
+ // Fake-tool path: last content block is a structured_output toolUse
2527
+ const content = (bedrockResponse.output?.message?.content ??
2528
+ []);
2529
+ const last = content[content.length - 1];
2530
+ return (!!last &&
2531
+ "toolUse" in last &&
2532
+ last.toolUse.name === STRUCTURED_OUTPUT_TOOL_NAME$2);
1291
2533
  }
1292
2534
  extractStructuredOutput(response) {
1293
- const anthropicResponse = response;
1294
- const lastBlock = anthropicResponse.content[anthropicResponse.content.length - 1];
1295
- if (lastBlock?.type === "tool_use" &&
1296
- lastBlock.name === STRUCTURED_OUTPUT_TOOL_NAME$2) {
1297
- return lastBlock.input;
2535
+ const bedrockResponse = response;
2536
+ if (bedrockResponse.__jaypieStructuredOutput) {
2537
+ const content = (bedrockResponse.output?.message?.content ??
2538
+ []);
2539
+ const textBlock = content.find((b) => "text" in b);
2540
+ if (!textBlock)
2541
+ return undefined;
2542
+ return extractJson(textBlock.text);
2543
+ }
2544
+ // Fake-tool path
2545
+ const content = (bedrockResponse.output?.message?.content ??
2546
+ []);
2547
+ const last = content[content.length - 1];
2548
+ if (last &&
2549
+ "toolUse" in last &&
2550
+ last.toolUse.name === STRUCTURED_OUTPUT_TOOL_NAME$2) {
2551
+ return last.toolUse.input;
1298
2552
  }
1299
2553
  return undefined;
1300
2554
  }
1301
2555
  //
2556
+ // Completion Detection
2557
+ //
2558
+ isComplete(response) {
2559
+ const bedrockResponse = response;
2560
+ return bedrockResponse.stopReason !== "tool_use";
2561
+ }
2562
+ //
1302
2563
  // Private Helpers
1303
2564
  //
1304
- extractContent(response) {
1305
- // Check for structured output first
1306
- if (this.hasStructuredOutput(response)) {
1307
- return this.extractStructuredOutput(response);
1308
- }
1309
- // Extract text content
1310
- const textBlock = response.content.find((block) => block.type === "text");
2565
+ extractContentFromMessage(message) {
2566
+ if (!message?.content)
2567
+ return undefined;
2568
+ const content = message.content;
2569
+ const textBlock = content.find((block) => "text" in block);
1311
2570
  return textBlock?.text;
1312
2571
  }
1313
2572
  }
1314
- // Export singleton instance
1315
- const anthropicAdapter = new AnthropicAdapter();
2573
+ const bedrockAdapter = new BedrockAdapter();
1316
2574
 
1317
2575
  //
1318
2576
  //
1319
2577
  // Constants
1320
2578
  //
1321
2579
  const STRUCTURED_OUTPUT_TOOL_NAME$1 = "structured_output";
2580
+ // Gemini 3 family supports combining tools (function calling) with native
2581
+ // structured output via `responseJsonSchema`. Earlier Gemini families
2582
+ // (including 2.5 thinking) do not support the combo and fall back to the
2583
+ // legacy `structured_output` fake-tool emulation.
2584
+ const GEMINI_3_PATTERN = /^gemini-3/;
2585
+ /**
2586
+ * Detect 4xx errors that indicate the model itself does not support the
2587
+ * `responseJsonSchema` + tools combo. Triggers the runtime fallback to the
2588
+ * fake-tool emulation path. Other 400s propagate.
2589
+ */
2590
+ function isStructuredOutputComboUnsupportedError(error) {
2591
+ if (!error || typeof error !== "object")
2592
+ return false;
2593
+ const err = error;
2594
+ const status = err.status ?? err.code;
2595
+ if (status !== 400)
2596
+ return false;
2597
+ const messages = [err.message, err.error?.message].filter((m) => typeof m === "string");
2598
+ return messages.some((m) => /response[_ ]?json[_ ]?schema|response[_ ]?schema|response[_ ]?mime|function[_ ]?call|tools/i.test(m));
2599
+ }
1322
2600
  // Gemini uses HTTP status codes for error classification
1323
2601
  // Documented at: https://ai.google.dev/api/rest/v1beta/Status
1324
2602
  const RETRYABLE_STATUS_CODES$1 = [
@@ -1342,15 +2620,30 @@ const NOT_RETRYABLE_STATUS_CODES = [
1342
2620
  // Main
1343
2621
  //
1344
2622
  /**
1345
- * GeminiAdapter implements the ProviderAdapter interface for Google's Gemini API.
2623
+ * GoogleAdapter implements the ProviderAdapter interface for Google's Gemini API.
1346
2624
  * It handles request building, response parsing, and error classification
1347
2625
  * specific to Gemini's generateContent API.
1348
2626
  */
1349
- class GeminiAdapter extends BaseProviderAdapter {
2627
+ class GoogleAdapter extends BaseProviderAdapter {
1350
2628
  constructor() {
1351
2629
  super(...arguments);
1352
- this.name = PROVIDER.GEMINI.NAME;
1353
- this.defaultModel = PROVIDER.GEMINI.MODEL.DEFAULT;
2630
+ this.name = PROVIDER.GOOGLE.NAME;
2631
+ this.defaultModel = PROVIDER.GOOGLE.MODEL.DEFAULT;
2632
+ // Session-level cache of Gemini 3 models observed to reject the native
2633
+ // `responseJsonSchema` + tools combo. When a model is in this set,
2634
+ // buildRequest engages the legacy fake-tool path instead.
2635
+ this.runtimeNoStructuredOutputComboModels = new Set();
2636
+ }
2637
+ rememberModelRejectsStructuredOutputCombo(model) {
2638
+ this.runtimeNoStructuredOutputComboModels.add(model);
2639
+ }
2640
+ clearRuntimeNoStructuredOutputComboModels() {
2641
+ this.runtimeNoStructuredOutputComboModels.clear();
2642
+ }
2643
+ supportsStructuredOutputCombo(model) {
2644
+ if (this.runtimeNoStructuredOutputComboModels.has(model))
2645
+ return false;
2646
+ return GEMINI_3_PATTERN.test(model);
1354
2647
  }
1355
2648
  //
1356
2649
  // Request Building
@@ -1379,9 +2672,27 @@ class GeminiAdapter extends BaseProviderAdapter {
1379
2672
  }
1380
2673
  }
1381
2674
  }
1382
- // Add tools if provided
1383
- if (request.tools && request.tools.length > 0) {
1384
- const functionDeclarations = request.tools.map((tool) => ({
2675
+ const hasUserTools = !!(request.tools && request.tools.length > 0);
2676
+ const useNativeCombo = Boolean(request.format) &&
2677
+ hasUserTools &&
2678
+ this.supportsStructuredOutputCombo(geminiRequest.model);
2679
+ // When tools+format are combined and the model does not support the native
2680
+ // combo, inject the legacy `structured_output` fake tool here so the model
2681
+ // is forced to call it before its final answer.
2682
+ const allTools = request.tools
2683
+ ? [...request.tools]
2684
+ : [];
2685
+ if (request.format && hasUserTools && !useNativeCombo) {
2686
+ log$1.warn(`[GoogleAdapter] Using legacy structured_output tool fallback for model ${geminiRequest.model}; native responseJsonSchema + tools combo is only available on Gemini 3.`);
2687
+ allTools.push({
2688
+ name: STRUCTURED_OUTPUT_TOOL_NAME$1,
2689
+ description: "Output a structured JSON object, " +
2690
+ "use this before your final response to give structured outputs to the user",
2691
+ parameters: request.format,
2692
+ });
2693
+ }
2694
+ if (allTools.length > 0) {
2695
+ const functionDeclarations = allTools.map((tool) => ({
1385
2696
  name: tool.name,
1386
2697
  description: tool.description,
1387
2698
  parameters: tool.parameters,
@@ -1391,21 +2702,33 @@ class GeminiAdapter extends BaseProviderAdapter {
1391
2702
  tools: [{ functionDeclarations }],
1392
2703
  };
1393
2704
  }
1394
- // Add structured output format if provided (but NOT when tools are present)
1395
- // Gemini doesn't support combining function calling with responseMimeType: 'application/json'
1396
- // When tools are present, structured output is handled via the structured_output tool
1397
- if (request.format && !(request.tools && request.tools.length > 0)) {
1398
- geminiRequest.config = {
1399
- ...geminiRequest.config,
1400
- responseMimeType: "application/json",
1401
- responseJsonSchema: request.format,
1402
- };
2705
+ // Native structured output: send schema as `responseJsonSchema`
2706
+ // (or `responseSchema` for Gemini 2.5+ no-tools path). The legacy
2707
+ // fake-tool emulation only runs when format+tools is combined on a model
2708
+ // that doesn't support the native combo.
2709
+ const wantsNativeStructured = Boolean(request.format) && (!hasUserTools || useNativeCombo);
2710
+ if (wantsNativeStructured) {
2711
+ const useJsonSchema = useNativeCombo || request.providerOptions?.useJsonSchema === true;
2712
+ if (useJsonSchema) {
2713
+ geminiRequest.config = {
2714
+ ...geminiRequest.config,
2715
+ responseMimeType: "application/json",
2716
+ responseJsonSchema: request.format,
2717
+ };
2718
+ }
2719
+ else {
2720
+ geminiRequest.config = {
2721
+ ...geminiRequest.config,
2722
+ responseMimeType: "application/json",
2723
+ responseSchema: jsonSchemaToOpenApi3(request.format),
2724
+ };
2725
+ }
1403
2726
  }
1404
- // When format is specified with tools, add instruction to use structured_output tool
1405
- if (request.format && request.tools && request.tools.length > 0) {
2727
+ // Legacy fake-tool path needs a system-prompt nudge so the model actually
2728
+ // calls the synthetic tool before its final answer.
2729
+ if (request.format && hasUserTools && !useNativeCombo) {
1406
2730
  const structuredOutputInstruction = "IMPORTANT: Before providing your final response, you MUST use the structured_output tool " +
1407
2731
  "to output your answer in the required JSON format.";
1408
- // Add to system instruction if it exists, otherwise create one
1409
2732
  const existingSystem = geminiRequest.config?.systemInstruction || "";
1410
2733
  geminiRequest.config = {
1411
2734
  ...geminiRequest.config,
@@ -1430,8 +2753,14 @@ class GeminiAdapter extends BaseProviderAdapter {
1430
2753
  }
1431
2754
  return geminiRequest;
1432
2755
  }
1433
- formatTools(toolkit, outputSchema) {
1434
- const tools = toolkit.tools.map((tool) => ({
2756
+ formatTools(toolkit,
2757
+ // outputSchema is part of the interface contract but Gemini now handles
2758
+ // structured output via `responseJsonSchema`/`responseSchema` (or the
2759
+ // legacy fake-tool injected in buildRequest as a fallback). We no longer
2760
+ // inject a synthetic structured-output tool here.
2761
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
2762
+ _outputSchema) {
2763
+ return toolkit.tools.map((tool) => ({
1435
2764
  name: tool.name,
1436
2765
  description: tool.description,
1437
2766
  parameters: {
@@ -1439,16 +2768,6 @@ class GeminiAdapter extends BaseProviderAdapter {
1439
2768
  type: "object",
1440
2769
  },
1441
2770
  }));
1442
- // Add structured output tool if schema is provided
1443
- if (outputSchema) {
1444
- tools.push({
1445
- name: STRUCTURED_OUTPUT_TOOL_NAME$1,
1446
- description: "Output a structured JSON object, " +
1447
- "use this before your final response to give structured outputs to the user",
1448
- parameters: outputSchema,
1449
- });
1450
- }
1451
- return tools;
1452
2771
  }
1453
2772
  formatOutputSchema(schema) {
1454
2773
  let jsonSchema;
@@ -1467,28 +2786,80 @@ class GeminiAdapter extends BaseProviderAdapter {
1467
2786
  : naturalZodSchema(schema);
1468
2787
  jsonSchema = v4.z.toJSONSchema(zodSchema);
1469
2788
  }
1470
- // Remove $schema property (Gemini doesn't need it)
1471
- if (jsonSchema.$schema) {
1472
- delete jsonSchema.$schema;
1473
- }
1474
- return jsonSchema;
2789
+ return jsonSchemaToOpenApi3(jsonSchema);
1475
2790
  }
1476
2791
  //
1477
2792
  // API Execution
1478
2793
  //
1479
- async executeRequest(client, request) {
2794
+ async executeRequest(client, request, signal) {
1480
2795
  const genAI = client;
1481
2796
  const geminiRequest = request;
1482
- // Cast config to any to bypass strict type checking between our internal types
1483
- // and the SDK's types. The SDK will validate at runtime.
1484
- const response = await genAI.models.generateContent({
1485
- model: geminiRequest.model,
1486
- contents: geminiRequest.contents,
1487
- config: geminiRequest.config,
1488
- });
1489
- return response;
2797
+ const wantsNativeCombo = !!geminiRequest.config?.responseJsonSchema &&
2798
+ !!geminiRequest.config?.tools;
2799
+ try {
2800
+ // Cast config to any to bypass strict type checking between our internal types
2801
+ // and the SDK's types. The SDK will validate at runtime.
2802
+ const response = await genAI.models.generateContent({
2803
+ model: geminiRequest.model,
2804
+ contents: geminiRequest.contents,
2805
+ config: geminiRequest.config,
2806
+ });
2807
+ return response;
2808
+ }
2809
+ catch (error) {
2810
+ if (signal?.aborted)
2811
+ return undefined;
2812
+ // If the model rejected the native responseJsonSchema + tools combo,
2813
+ // cache it and retry with the legacy fake-tool emulation path.
2814
+ if (wantsNativeCombo && isStructuredOutputComboUnsupportedError(error)) {
2815
+ const model = geminiRequest.model;
2816
+ this.rememberModelRejectsStructuredOutputCombo(model);
2817
+ log$1.warn(`[GoogleAdapter] Model ${model} rejected native responseJsonSchema + tools combo; falling back to legacy structured_output tool emulation.`);
2818
+ const fallbackRequest = this.toFallbackStructuredOutputRequest(geminiRequest);
2819
+ const response = await genAI.models.generateContent({
2820
+ model: fallbackRequest.model,
2821
+ contents: fallbackRequest.contents,
2822
+ config: fallbackRequest.config,
2823
+ });
2824
+ return response;
2825
+ }
2826
+ throw error;
2827
+ }
2828
+ }
2829
+ /**
2830
+ * Rebuild a Gemini 3 native-combo request without `responseJsonSchema`/
2831
+ * `responseMimeType`, swapping in the legacy fake-tool emulation. Used as
2832
+ * a runtime fallback when a Gemini 3 model rejects the combo.
2833
+ */
2834
+ toFallbackStructuredOutputRequest(request) {
2835
+ if (!request.config?.responseJsonSchema)
2836
+ return request;
2837
+ const schema = request.config.responseJsonSchema;
2838
+ const newConfig = { ...request.config };
2839
+ delete newConfig.responseJsonSchema;
2840
+ delete newConfig.responseMimeType;
2841
+ const fakeTool = {
2842
+ name: STRUCTURED_OUTPUT_TOOL_NAME$1,
2843
+ description: "Output a structured JSON object, " +
2844
+ "use this before your final response to give structured outputs to the user",
2845
+ parameters: schema,
2846
+ };
2847
+ const existingDeclarations = newConfig.tools?.[0]?.functionDeclarations ?? [];
2848
+ newConfig.tools = [
2849
+ { functionDeclarations: [...existingDeclarations, fakeTool] },
2850
+ ];
2851
+ const structuredOutputInstruction = "IMPORTANT: Before providing your final response, you MUST use the structured_output tool " +
2852
+ "to output your answer in the required JSON format.";
2853
+ const existingSystem = newConfig.systemInstruction || "";
2854
+ newConfig.systemInstruction = existingSystem
2855
+ ? `${existingSystem}\n\n${structuredOutputInstruction}`
2856
+ : structuredOutputInstruction;
2857
+ return {
2858
+ ...request,
2859
+ config: newConfig,
2860
+ };
1490
2861
  }
1491
- async *executeStreamRequest(client, request) {
2862
+ async *executeStreamRequest(client, request, signal) {
1492
2863
  const genAI = client;
1493
2864
  const geminiRequest = request;
1494
2865
  // Use generateContentStream for streaming
@@ -1497,8 +2868,6 @@ class GeminiAdapter extends BaseProviderAdapter {
1497
2868
  contents: geminiRequest.contents,
1498
2869
  config: geminiRequest.config,
1499
2870
  });
1500
- // Track current function call being built
1501
- let currentFunctionCall = null;
1502
2871
  // Track usage for final chunk
1503
2872
  let inputTokens = 0;
1504
2873
  let outputTokens = 0;
@@ -1519,11 +2888,16 @@ class GeminiAdapter extends BaseProviderAdapter {
1519
2888
  // Handle function calls
1520
2889
  if (part.functionCall) {
1521
2890
  const functionCall = part.functionCall;
1522
- currentFunctionCall = {
2891
+ const currentFunctionCall = {
1523
2892
  id: functionCall.id || this.generateCallId(),
1524
2893
  name: functionCall.name || "",
1525
2894
  arguments: functionCall.args || {},
1526
2895
  };
2896
+ // Preserve thoughtSignature for Gemini 3 models
2897
+ // Required to maintain tool call context between turns
2898
+ const metadata = part.thoughtSignature
2899
+ ? { thoughtSignature: part.thoughtSignature }
2900
+ : undefined;
1527
2901
  // Emit the function call immediately
1528
2902
  yield {
1529
2903
  type: exports.LlmStreamChunkType.ToolCall,
@@ -1531,9 +2905,9 @@ class GeminiAdapter extends BaseProviderAdapter {
1531
2905
  id: currentFunctionCall.id,
1532
2906
  name: currentFunctionCall.name,
1533
2907
  arguments: JSON.stringify(currentFunctionCall.arguments),
2908
+ metadata,
1534
2909
  },
1535
2910
  };
1536
- currentFunctionCall = null;
1537
2911
  }
1538
2912
  }
1539
2913
  }
@@ -1620,14 +2994,24 @@ class GeminiAdapter extends BaseProviderAdapter {
1620
2994
  // Tool Result Handling
1621
2995
  //
1622
2996
  formatToolResult(toolCall, result) {
1623
- // Gemini expects the response to be the actual result object, not wrapped in "result"
1624
- // The output from StandardToolResult is JSON-stringified, so we need to parse it
2997
+ // Gemini's `function_response.response` is a protobuf Struct, which only
2998
+ // accepts plain objects. Some models (e.g. gemini-3.1-pro) accept scalar
2999
+ // values silently; gemini-3.1-flash-lite rejects them. Parse the output
3000
+ // and wrap any non-object value (string, number, boolean, array, null)
3001
+ // in `{ result: value }` so every tool result is a valid Struct.
1625
3002
  let responseData;
1626
3003
  try {
1627
- responseData = JSON.parse(result.output);
3004
+ const parsed = JSON.parse(result.output);
3005
+ if (parsed !== null &&
3006
+ typeof parsed === "object" &&
3007
+ !Array.isArray(parsed)) {
3008
+ responseData = parsed;
3009
+ }
3010
+ else {
3011
+ responseData = { result: parsed };
3012
+ }
1628
3013
  }
1629
3014
  catch {
1630
- // If parsing fails, wrap the output as a string result
1631
3015
  responseData = { result: result.output };
1632
3016
  }
1633
3017
  return {
@@ -1750,6 +3134,14 @@ class GeminiAdapter extends BaseProviderAdapter {
1750
3134
  shouldRetry: true,
1751
3135
  };
1752
3136
  }
3137
+ // Check for transient network errors (ECONNRESET, etc.)
3138
+ if (isTransientNetworkError(error)) {
3139
+ return {
3140
+ error,
3141
+ category: ErrorCategory.Retryable,
3142
+ shouldRetry: true,
3143
+ };
3144
+ }
1753
3145
  // Unknown error - treat as potentially retryable
1754
3146
  return {
1755
3147
  error,
@@ -1983,7 +3375,17 @@ class GeminiAdapter extends BaseProviderAdapter {
1983
3375
  return JSON.parse(textContent);
1984
3376
  }
1985
3377
  catch {
1986
- // If parsing fails, return the original string
3378
+ // Strip markdown code fences and retry (Gemini sometimes wraps JSON in fences)
3379
+ const jsonMatch = textContent.match(/```(?:json)?\s*([\s\S]*?)\s*```/) ||
3380
+ textContent.match(/\{[\s\S]*\}/);
3381
+ if (jsonMatch) {
3382
+ try {
3383
+ return JSON.parse(jsonMatch[1] || jsonMatch[0]);
3384
+ }
3385
+ catch {
3386
+ // Fall through to return original string
3387
+ }
3388
+ }
1987
3389
  return textContent;
1988
3390
  }
1989
3391
  }
@@ -1994,7 +3396,7 @@ class GeminiAdapter extends BaseProviderAdapter {
1994
3396
  }
1995
3397
  }
1996
3398
  // Export singleton instance
1997
- const geminiAdapter = new GeminiAdapter();
3399
+ const googleAdapter = new GoogleAdapter();
1998
3400
 
1999
3401
  // Patterns for OpenAI reasoning models that support extended thinking
2000
3402
  const REASONING_MODEL_PATTERNS = [
@@ -2026,6 +3428,27 @@ const NOT_RETRYABLE_ERROR_TYPES = [
2026
3428
  openai.RateLimitError,
2027
3429
  openai.UnprocessableEntityError,
2028
3430
  ];
3431
+ // Models known not to accept `temperature`.
3432
+ // Patterns (not exact names) so dated variants and future releases are covered
3433
+ // without code changes — OpenAI is removing temperature on newer reasoning
3434
+ // models.
3435
+ const MODELS_WITHOUT_TEMPERATURE$1 = [
3436
+ /^gpt-5\.5/, // gpt-5.5 series deprecated temperature
3437
+ /^o\d/, // o-series reasoning models (o1, o3, o4, ...)
3438
+ ];
3439
+ function isTemperatureDeprecationError$1(error) {
3440
+ if (!error || typeof error !== "object")
3441
+ return false;
3442
+ if (!(error instanceof openai.BadRequestError) &&
3443
+ error.status !== 400) {
3444
+ return false;
3445
+ }
3446
+ const messages = [
3447
+ error.message,
3448
+ error.error?.message,
3449
+ ].filter((m) => typeof m === "string");
3450
+ return messages.some((m) => m.toLowerCase().includes("temperature"));
3451
+ }
2029
3452
  //
2030
3453
  //
2031
3454
  // Main
@@ -2040,6 +3463,20 @@ class OpenAiAdapter extends BaseProviderAdapter {
2040
3463
  super(...arguments);
2041
3464
  this.name = PROVIDER.OPENAI.NAME;
2042
3465
  this.defaultModel = PROVIDER.OPENAI.MODEL.DEFAULT;
3466
+ // Session-level cache of models observed to reject `temperature` at runtime.
3467
+ // Populated by executeRequest on 400 errors so repeat calls skip the param.
3468
+ this.runtimeNoTemperatureModels = new Set();
3469
+ }
3470
+ rememberModelRejectsTemperature(model) {
3471
+ this.runtimeNoTemperatureModels.add(model);
3472
+ }
3473
+ clearRuntimeNoTemperatureModels() {
3474
+ this.runtimeNoTemperatureModels.clear();
3475
+ }
3476
+ supportsTemperature(model) {
3477
+ if (this.runtimeNoTemperatureModels.has(model))
3478
+ return false;
3479
+ return !MODELS_WITHOUT_TEMPERATURE$1.some((pattern) => pattern.test(model));
2043
3480
  }
2044
3481
  //
2045
3482
  // Request Building
@@ -2048,7 +3485,7 @@ class OpenAiAdapter extends BaseProviderAdapter {
2048
3485
  const model = request.model || this.defaultModel;
2049
3486
  const openaiRequest = {
2050
3487
  model,
2051
- input: request.messages,
3488
+ input: this.sanitizeInputItems(request.messages),
2052
3489
  };
2053
3490
  if (request.user) {
2054
3491
  openaiRequest.user = request.user;
@@ -2083,6 +3520,11 @@ class OpenAiAdapter extends BaseProviderAdapter {
2083
3520
  if (request.temperature !== undefined) {
2084
3521
  openaiRequest.temperature = request.temperature;
2085
3522
  }
3523
+ // Strip temperature for models that don't support it (denylist + runtime cache)
3524
+ if (openaiRequest.temperature !== undefined &&
3525
+ !this.supportsTemperature(openaiRequest.model)) {
3526
+ delete openaiRequest.temperature;
3527
+ }
2086
3528
  return openaiRequest;
2087
3529
  }
2088
3530
  formatTools(toolkit, _outputSchema) {
@@ -2130,19 +3572,34 @@ class OpenAiAdapter extends BaseProviderAdapter {
2130
3572
  //
2131
3573
  // API Execution
2132
3574
  //
2133
- async executeRequest(client, request) {
3575
+ async executeRequest(client, request, signal) {
2134
3576
  const openai = client;
2135
- // @ts-expect-error OpenAI SDK types don't match our request format exactly
2136
- return await openai.responses.create(request);
3577
+ const openaiRequest = request;
3578
+ try {
3579
+ return await openai.responses.create(openaiRequest, signal ? { signal } : undefined);
3580
+ }
3581
+ catch (error) {
3582
+ if (signal?.aborted)
3583
+ return undefined;
3584
+ // If the model rejected `temperature`, cache it and retry without the param
3585
+ if (openaiRequest.temperature !== undefined &&
3586
+ isTemperatureDeprecationError$1(error)) {
3587
+ this.rememberModelRejectsTemperature(openaiRequest.model);
3588
+ const retryRequest = { ...openaiRequest };
3589
+ delete retryRequest.temperature;
3590
+ return await openai.responses.create(retryRequest, signal ? { signal } : undefined);
3591
+ }
3592
+ throw error;
3593
+ }
2137
3594
  }
2138
- async *executeStreamRequest(client, request) {
3595
+ async *executeStreamRequest(client, request, signal) {
2139
3596
  const openai = client;
2140
3597
  const baseRequest = request;
2141
3598
  const streamRequest = {
2142
3599
  ...baseRequest,
2143
3600
  stream: true,
2144
3601
  };
2145
- const stream = await openai.responses.create(streamRequest);
3602
+ const stream = await openai.responses.create(streamRequest, signal ? { signal } : undefined);
2146
3603
  // Track current function call being built
2147
3604
  let currentFunctionCall = null;
2148
3605
  // Track usage for final chunk
@@ -2192,6 +3649,9 @@ class OpenAiAdapter extends BaseProviderAdapter {
2192
3649
  id: currentFunctionCall.callId,
2193
3650
  name: currentFunctionCall.name,
2194
3651
  arguments: currentFunctionCall.arguments,
3652
+ // Preserve the item ID (fc_...) separately from call_id (call_...)
3653
+ // OpenAI Responses API requires both with correct prefixes
3654
+ metadata: { itemId: currentFunctionCall.id },
2195
3655
  },
2196
3656
  };
2197
3657
  currentFunctionCall = null;
@@ -2352,6 +3812,14 @@ class OpenAiAdapter extends BaseProviderAdapter {
2352
3812
  };
2353
3813
  }
2354
3814
  }
3815
+ // Check for transient network errors (ECONNRESET, etc.)
3816
+ if (isTransientNetworkError(error)) {
3817
+ return {
3818
+ error,
3819
+ category: ErrorCategory.Retryable,
3820
+ shouldRetry: true,
3821
+ };
3822
+ }
2355
3823
  // Unknown error - treat as potentially retryable
2356
3824
  return {
2357
3825
  error,
@@ -2378,6 +3846,31 @@ class OpenAiAdapter extends BaseProviderAdapter {
2378
3846
  //
2379
3847
  // Private Helpers
2380
3848
  //
3849
+ /**
3850
+ * Sanitize history items for the OpenAI Responses API.
3851
+ * Strips fields not accepted by specific item types (e.g., `name` on
3852
+ * function_call_output items) to prevent 400 "Unknown parameter" errors.
3853
+ */
3854
+ sanitizeInputItems(messages) {
3855
+ return messages.map((item) => {
3856
+ const typedItem = item;
3857
+ // function_call_output only accepts: type, call_id, output, id, status
3858
+ if (typedItem.type === exports.LlmMessageType.FunctionCallOutput) {
3859
+ const sanitized = {
3860
+ type: typedItem.type,
3861
+ call_id: typedItem.call_id,
3862
+ output: typedItem.output,
3863
+ };
3864
+ if (typedItem.id)
3865
+ sanitized.id = typedItem.id;
3866
+ if (typedItem.status)
3867
+ sanitized.status = typedItem.status;
3868
+ return sanitized;
3869
+ }
3870
+ // All other items pass through as-is
3871
+ return item;
3872
+ });
3873
+ }
2381
3874
  hasToolCalls(response) {
2382
3875
  if (!response.output || !Array.isArray(response.output)) {
2383
3876
  return false;
@@ -2423,10 +3916,48 @@ const openAiAdapter = new OpenAiAdapter();
2423
3916
  // Constants
2424
3917
  //
2425
3918
  const STRUCTURED_OUTPUT_TOOL_NAME = "structured_output";
3919
+ const STRUCTURED_OUTPUT_SCHEMA_NAME = "response";
3920
+ /**
3921
+ * Detect 4xx errors that indicate the model itself does not support
3922
+ * `response_format: json_schema`. Mirrors the Anthropic pattern: we only
3923
+ * trigger the fake-tool fallback when the failure is plausibly a capability
3924
+ * gap, not a generic 400.
3925
+ */
3926
+ function isStructuredOutputUnsupportedError(error) {
3927
+ if (!error || typeof error !== "object")
3928
+ return false;
3929
+ const err = error;
3930
+ const status = err.status ?? err.statusCode;
3931
+ if (status !== 400 && status !== 422)
3932
+ return false;
3933
+ const messages = [err.message, err.error?.message].filter((m) => typeof m === "string");
3934
+ return messages.some((m) => /response_format|json[_ ]schema|structured[_ ]output|require[_ ]parameters/i.test(m));
3935
+ }
3936
+ // OpenRouter routes that don't accept `temperature`. Patterns match the
3937
+ // vendor-prefixed route id (e.g. `openai/gpt-5.5`) so dated variants are
3938
+ // covered without code changes.
3939
+ const MODELS_WITHOUT_TEMPERATURE = [
3940
+ /^openai\/gpt-5\.5/,
3941
+ /^openai\/o\d/,
3942
+ /^anthropic\/claude-opus-4-[789]/,
3943
+ /^anthropic\/claude-opus-[5-9]/,
3944
+ ];
3945
+ function isTemperatureDeprecationError(error) {
3946
+ if (!error || typeof error !== "object")
3947
+ return false;
3948
+ const err = error;
3949
+ const status = err.status ?? err.statusCode;
3950
+ if (status !== 400)
3951
+ return false;
3952
+ const messages = [err.message, err.error?.message].filter((m) => typeof m === "string");
3953
+ return messages.some((m) => m.toLowerCase().includes("temperature"));
3954
+ }
2426
3955
  /**
2427
- * Convert standardized content items to OpenRouter format
2428
- * Note: OpenRouter does not support native file/image uploads.
2429
- * Images and files are discarded with a warning.
3956
+ * Convert standardized content items to OpenRouter format. Images become
3957
+ * `image_url` parts and files become `file` parts; both pass through to
3958
+ * OpenRouter which routes to the selected backend. Backends that don't
3959
+ * support the modality 4xx — that's a model-capability mismatch, surfaced
3960
+ * by the call rather than silently dropped here.
2430
3961
  */
2431
3962
  function convertContentToOpenRouter(content) {
2432
3963
  if (typeof content === "string") {
@@ -2434,25 +3965,38 @@ function convertContentToOpenRouter(content) {
2434
3965
  }
2435
3966
  const parts = [];
2436
3967
  for (const item of content) {
2437
- // Text content - pass through
2438
3968
  if (item.type === exports.LlmMessageType.InputText) {
2439
3969
  parts.push({ type: "text", text: item.text });
2440
3970
  continue;
2441
3971
  }
2442
- // Image content - warn and discard
2443
3972
  if (item.type === exports.LlmMessageType.InputImage) {
2444
- log$2.log.warn("OpenRouter does not support image uploads; image discarded");
3973
+ const url = item.image_url ?? "";
3974
+ if (!url) {
3975
+ log$1.log.warn("OpenRouter image content missing image_url; image discarded");
3976
+ continue;
3977
+ }
3978
+ parts.push({ type: "image_url", imageUrl: { url } });
2445
3979
  continue;
2446
3980
  }
2447
- // File/Document content - warn and discard
2448
3981
  if (item.type === exports.LlmMessageType.InputFile) {
2449
- log$2.log.warn({ filename: item.filename }, "OpenRouter does not support file uploads; file discarded");
3982
+ const fileData = typeof item.file_data === "string" ? item.file_data : "";
3983
+ if (!fileData) {
3984
+ log$1.log.warn({ filename: item.filename }, "OpenRouter file content missing file_data; file discarded");
3985
+ continue;
3986
+ }
3987
+ parts.push({
3988
+ type: "file",
3989
+ file: {
3990
+ filename: item.filename,
3991
+ fileData,
3992
+ },
3993
+ });
2450
3994
  continue;
2451
3995
  }
2452
3996
  // Unknown type - warn and skip
2453
- log$2.log.warn({ item }, "Unknown content type for OpenRouter; discarded");
3997
+ log$1.log.warn({ item }, "Unknown content type for OpenRouter; discarded");
2454
3998
  }
2455
- // If no text parts remain, return empty string to avoid empty array
3999
+ // If no parts remain, return empty string to avoid empty array
2456
4000
  if (parts.length === 0) {
2457
4001
  return "";
2458
4002
  }
@@ -2461,6 +4005,32 @@ function convertContentToOpenRouter(content) {
2461
4005
  // OpenRouter SDK error types based on HTTP status codes
2462
4006
  const RETRYABLE_STATUS_CODES = [408, 500, 502, 503, 524, 529];
2463
4007
  const RATE_LIMIT_STATUS_CODE = 429;
4008
+ /**
4009
+ * Walk the JSON schema and force `additionalProperties: false` on every
4010
+ * object node. Required by the OpenAI-style json_schema response_format
4011
+ * (which OpenRouter accepts) when `strict: true`.
4012
+ */
4013
+ function enforceAdditionalPropertiesFalse(schema) {
4014
+ const stack = [schema];
4015
+ while (stack.length > 0) {
4016
+ const node = stack.pop();
4017
+ if (node.type === "object") {
4018
+ node.additionalProperties = false;
4019
+ }
4020
+ for (const value of Object.values(node)) {
4021
+ if (Array.isArray(value)) {
4022
+ for (const entry of value) {
4023
+ if (entry && typeof entry === "object" && !Array.isArray(entry)) {
4024
+ stack.push(entry);
4025
+ }
4026
+ }
4027
+ }
4028
+ else if (value && typeof value === "object") {
4029
+ stack.push(value);
4030
+ }
4031
+ }
4032
+ }
4033
+ }
2464
4034
  //
2465
4035
  //
2466
4036
  // Main
@@ -2476,6 +4046,33 @@ class OpenRouterAdapter extends BaseProviderAdapter {
2476
4046
  super(...arguments);
2477
4047
  this.name = PROVIDER.OPENROUTER.NAME;
2478
4048
  this.defaultModel = PROVIDER.OPENROUTER.MODEL.DEFAULT;
4049
+ // Session-level cache of models observed to reject native
4050
+ // `response_format: json_schema`. When a model is in this set, buildRequest
4051
+ // engages the legacy fake-tool path instead of native structured output.
4052
+ this.runtimeNoStructuredOutputModels = new Set();
4053
+ // Session-level cache of routes observed to reject `temperature`. Populated
4054
+ // by executeRequest on 400 errors so repeat calls skip the param.
4055
+ this.runtimeNoTemperatureModels = new Set();
4056
+ }
4057
+ rememberModelRejectsStructuredOutput(model) {
4058
+ this.runtimeNoStructuredOutputModels.add(model);
4059
+ }
4060
+ clearRuntimeNoStructuredOutputModels() {
4061
+ this.runtimeNoStructuredOutputModels.clear();
4062
+ }
4063
+ supportsStructuredOutput(model) {
4064
+ return !this.runtimeNoStructuredOutputModels.has(model);
4065
+ }
4066
+ rememberModelRejectsTemperature(model) {
4067
+ this.runtimeNoTemperatureModels.add(model);
4068
+ }
4069
+ clearRuntimeNoTemperatureModels() {
4070
+ this.runtimeNoTemperatureModels.clear();
4071
+ }
4072
+ supportsTemperature(model) {
4073
+ if (this.runtimeNoTemperatureModels.has(model))
4074
+ return false;
4075
+ return !MODELS_WITHOUT_TEMPERATURE.some((pattern) => pattern.test(model));
2479
4076
  }
2480
4077
  //
2481
4078
  // Request Building
@@ -2497,8 +4094,23 @@ class OpenRouterAdapter extends BaseProviderAdapter {
2497
4094
  if (request.user) {
2498
4095
  openRouterRequest.user = request.user;
2499
4096
  }
2500
- if (request.tools && request.tools.length > 0) {
2501
- openRouterRequest.tools = request.tools.map((tool) => ({
4097
+ const useFallbackStructuredOutput = Boolean(request.format) &&
4098
+ !this.supportsStructuredOutput(openRouterRequest.model);
4099
+ const allTools = request.tools
4100
+ ? [...request.tools]
4101
+ : [];
4102
+ if (useFallbackStructuredOutput && request.format) {
4103
+ log$1.log.warn(`[OpenRouterAdapter] Using legacy structured_output tool fallback for model ${openRouterRequest.model}; native response_format previously rejected for this model.`);
4104
+ allTools.push({
4105
+ name: STRUCTURED_OUTPUT_TOOL_NAME,
4106
+ description: "REQUIRED: You MUST call this tool to provide your final response. " +
4107
+ "After gathering all necessary information (including results from other tools), " +
4108
+ "call this tool with the structured data to complete the request.",
4109
+ parameters: request.format,
4110
+ });
4111
+ }
4112
+ if (allTools.length > 0) {
4113
+ openRouterRequest.tools = allTools.map((tool) => ({
2502
4114
  type: "function",
2503
4115
  function: {
2504
4116
  name: tool.name,
@@ -2510,6 +4122,19 @@ class OpenRouterAdapter extends BaseProviderAdapter {
2510
4122
  // The structured_output tool prompt already emphasizes it must be called
2511
4123
  openRouterRequest.tool_choice = "auto";
2512
4124
  }
4125
+ // Native structured output: send schema as `response_format`. The legacy
4126
+ // tool-emulation path is engaged only as a runtime fallback for models the
4127
+ // API has flagged as not supporting native json_schema.
4128
+ if (request.format && !useFallbackStructuredOutput) {
4129
+ openRouterRequest.response_format = {
4130
+ type: "json_schema",
4131
+ json_schema: {
4132
+ name: STRUCTURED_OUTPUT_SCHEMA_NAME,
4133
+ schema: request.format,
4134
+ strict: true,
4135
+ },
4136
+ };
4137
+ }
2513
4138
  if (request.providerOptions) {
2514
4139
  Object.assign(openRouterRequest, request.providerOptions);
2515
4140
  }
@@ -2518,26 +4143,26 @@ class OpenRouterAdapter extends BaseProviderAdapter {
2518
4143
  openRouterRequest.temperature =
2519
4144
  request.temperature;
2520
4145
  }
4146
+ // Strip temperature for routes that don't support it (denylist + runtime cache)
4147
+ const requestRecord = openRouterRequest;
4148
+ if (requestRecord.temperature !== undefined &&
4149
+ !this.supportsTemperature(openRouterRequest.model)) {
4150
+ delete requestRecord.temperature;
4151
+ }
2521
4152
  return openRouterRequest;
2522
4153
  }
2523
- formatTools(toolkit, outputSchema) {
2524
- const tools = toolkit.tools.map((tool) => ({
4154
+ formatTools(toolkit,
4155
+ // outputSchema is part of the interface contract but OpenRouter now uses
4156
+ // native `response_format` (set in buildRequest), so we no longer inject a
4157
+ // synthetic structured-output tool here. The legacy fake-tool injection
4158
+ // happens in buildRequest only as a runtime fallback.
4159
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
4160
+ _outputSchema) {
4161
+ return toolkit.tools.map((tool) => ({
2525
4162
  name: tool.name,
2526
4163
  description: tool.description,
2527
4164
  parameters: tool.parameters,
2528
4165
  }));
2529
- // Add structured output tool if schema is provided
2530
- // (OpenRouter doesn't have native structured output like OpenAI, so use tool approach)
2531
- if (outputSchema) {
2532
- tools.push({
2533
- name: STRUCTURED_OUTPUT_TOOL_NAME,
2534
- description: "REQUIRED: You MUST call this tool to provide your final response. " +
2535
- "After gathering all necessary information (including results from other tools), " +
2536
- "call this tool with the structured data to complete the request.",
2537
- parameters: outputSchema,
2538
- });
2539
- }
2540
- return tools;
2541
4166
  }
2542
4167
  formatOutputSchema(schema) {
2543
4168
  let jsonSchema;
@@ -2550,45 +4175,141 @@ class OpenRouterAdapter extends BaseProviderAdapter {
2550
4175
  jsonSchema.type = "object"; // Normalize type
2551
4176
  }
2552
4177
  else {
2553
- // Convert NaturalSchema to JSON schema through Zod
4178
+ // Convert NaturalSchema to JSON schema through Zod. Re-spread into a
4179
+ // plain object: Zod v4's z.toJSONSchema returns an object that carries
4180
+ // a non-configurable `~standard` Standard-Schema interop marker as a
4181
+ // non-enumerable own property. Anthropic's output_config.format
4182
+ // validation is strict and rejects unknown properties when OpenRouter
4183
+ // forwards the schema, so we drop the marker here. JSON.stringify
4184
+ // already skips it but the OpenRouter SDK enumerates own properties
4185
+ // during serialization.
2554
4186
  const zodSchema = schema instanceof v4.z.ZodType
2555
4187
  ? schema
2556
4188
  : naturalZodSchema(schema);
2557
- jsonSchema = v4.z.toJSONSchema(zodSchema);
4189
+ jsonSchema = { ...v4.z.toJSONSchema(zodSchema) };
2558
4190
  }
2559
4191
  // Remove $schema property (can cause issues with some providers)
2560
4192
  if (jsonSchema.$schema) {
2561
4193
  delete jsonSchema.$schema;
2562
4194
  }
4195
+ // OpenRouter (and most backends behind it) require additionalProperties:
4196
+ // false on every object when using strict json_schema response_format.
4197
+ enforceAdditionalPropertiesFalse(jsonSchema);
2563
4198
  return jsonSchema;
2564
4199
  }
2565
4200
  //
2566
4201
  // API Execution
2567
4202
  //
2568
- async executeRequest(client, request) {
4203
+ async executeRequest(client, request, signal) {
2569
4204
  const openRouter = client;
2570
4205
  const openRouterRequest = request;
2571
- const response = await openRouter.chat.send({
4206
+ const wantsStructuredOutput = Boolean(openRouterRequest.response_format);
4207
+ try {
4208
+ const response = (await openRouter.chat.send(this.toSdkChatParams(openRouterRequest), signal ? { signal } : undefined));
4209
+ if (wantsStructuredOutput) {
4210
+ response.__jaypieStructuredOutput = true;
4211
+ }
4212
+ return response;
4213
+ }
4214
+ catch (error) {
4215
+ if (signal?.aborted)
4216
+ return undefined;
4217
+ // If the route rejected `temperature` (e.g., openai/gpt-5.5 forwarding),
4218
+ // cache it and retry without the param.
4219
+ const requestRecord = openRouterRequest;
4220
+ if (requestRecord.temperature !== undefined &&
4221
+ isTemperatureDeprecationError(error)) {
4222
+ this.rememberModelRejectsTemperature(openRouterRequest.model);
4223
+ const retryRequest = { ...openRouterRequest };
4224
+ delete retryRequest.temperature;
4225
+ const response = (await openRouter.chat.send(this.toSdkChatParams(retryRequest), signal ? { signal } : undefined));
4226
+ if (wantsStructuredOutput) {
4227
+ response.__jaypieStructuredOutput = true;
4228
+ }
4229
+ return response;
4230
+ }
4231
+ // If the model rejected `response_format`, cache it and retry with the
4232
+ // legacy fake-tool emulation path.
4233
+ if (wantsStructuredOutput && isStructuredOutputUnsupportedError(error)) {
4234
+ const model = openRouterRequest.model;
4235
+ this.rememberModelRejectsStructuredOutput(model);
4236
+ log$1.log.warn(`[OpenRouterAdapter] Model ${model} rejected native response_format; falling back to legacy structured_output tool emulation.`);
4237
+ const fallbackRequest = this.toFallbackStructuredOutputRequest(openRouterRequest);
4238
+ return (await openRouter.chat.send(this.toSdkChatParams(fallbackRequest), signal ? { signal } : undefined));
4239
+ }
4240
+ throw error;
4241
+ }
4242
+ }
4243
+ /**
4244
+ * Translate our internal snake_case `OpenRouterRequest` into the SDK's
4245
+ * camelCase shape, forwarding only the fields we care about (the SDK
4246
+ * silently strips unknown fields).
4247
+ */
4248
+ toSdkChatParams(openRouterRequest) {
4249
+ const params = {
2572
4250
  model: openRouterRequest.model,
2573
4251
  messages: openRouterRequest.messages,
2574
4252
  tools: openRouterRequest.tools,
2575
4253
  toolChoice: openRouterRequest.tool_choice,
2576
4254
  user: openRouterRequest.user,
2577
- });
2578
- return response;
4255
+ };
4256
+ if (openRouterRequest.response_format) {
4257
+ const format = openRouterRequest.response_format;
4258
+ if (format.type === "json_schema") {
4259
+ params.responseFormat = {
4260
+ type: "json_schema",
4261
+ jsonSchema: format.json_schema,
4262
+ };
4263
+ }
4264
+ else {
4265
+ params.responseFormat = format;
4266
+ }
4267
+ }
4268
+ const temperature = openRouterRequest.temperature;
4269
+ if (temperature !== undefined) {
4270
+ params.temperature = temperature;
4271
+ }
4272
+ return params;
4273
+ }
4274
+ /**
4275
+ * Rebuild a structured-output request without `response_format`, swapping in
4276
+ * the legacy fake-tool emulation. Used as a runtime fallback when a model
4277
+ * rejects native json_schema.
4278
+ */
4279
+ toFallbackStructuredOutputRequest(request) {
4280
+ if (!request.response_format ||
4281
+ request.response_format.type !== "json_schema") {
4282
+ return request;
4283
+ }
4284
+ const { response_format, ...rest } = request;
4285
+ const fallbackRequest = { ...rest };
4286
+ const schema = response_format.json_schema.schema;
4287
+ const fakeTool = {
4288
+ type: "function",
4289
+ function: {
4290
+ name: STRUCTURED_OUTPUT_TOOL_NAME,
4291
+ description: "REQUIRED: You MUST call this tool to provide your final response. " +
4292
+ "After gathering all necessary information (including results from other tools), " +
4293
+ "call this tool with the structured data to complete the request.",
4294
+ parameters: schema,
4295
+ },
4296
+ };
4297
+ fallbackRequest.tools = [...(fallbackRequest.tools ?? []), fakeTool];
4298
+ fallbackRequest.tool_choice = "auto";
4299
+ return fallbackRequest;
2579
4300
  }
2580
- async *executeStreamRequest(client, request) {
4301
+ async *executeStreamRequest(client, request, signal) {
2581
4302
  const openRouter = client;
2582
4303
  const openRouterRequest = request;
2583
- // Use chat.send with stream: true for streaming responses
2584
- const stream = await openRouter.chat.send({
2585
- model: openRouterRequest.model,
2586
- messages: openRouterRequest.messages,
2587
- tools: openRouterRequest.tools,
2588
- toolChoice: openRouterRequest.tool_choice,
2589
- user: openRouterRequest.user,
4304
+ // Use chat.send with stream: true for streaming responses.
4305
+ // Cast the result to AsyncIterable: when stream: true, the SDK returns a
4306
+ // stream we can iterate, but the typed result is the union with the
4307
+ // non-stream response.
4308
+ const streamParams = {
4309
+ ...this.toSdkChatParams(openRouterRequest),
2590
4310
  stream: true,
2591
- });
4311
+ };
4312
+ const stream = (await openRouter.chat.send(streamParams, signal ? { signal } : undefined));
2592
4313
  // Track current tool call being built
2593
4314
  let currentToolCall = null;
2594
4315
  // Track usage for final chunk
@@ -2838,6 +4559,14 @@ class OpenRouterAdapter extends BaseProviderAdapter {
2838
4559
  suggestedDelayMs: 60000,
2839
4560
  };
2840
4561
  }
4562
+ // Check for transient network errors (ECONNRESET, etc.)
4563
+ if (isTransientNetworkError(error)) {
4564
+ return {
4565
+ error,
4566
+ category: ErrorCategory.Retryable,
4567
+ shouldRetry: true,
4568
+ };
4569
+ }
2841
4570
  // Unknown error - treat as potentially retryable
2842
4571
  return {
2843
4572
  error,
@@ -2859,6 +4588,13 @@ class OpenRouterAdapter extends BaseProviderAdapter {
2859
4588
  }
2860
4589
  hasStructuredOutput(response) {
2861
4590
  const openRouterResponse = response;
4591
+ // Native path: executeRequest annotates the response when we sent
4592
+ // `response_format`, so we can detect intent statelessly.
4593
+ if (openRouterResponse.__jaypieStructuredOutput) {
4594
+ return this.extractStructuredOutput(response) !== undefined;
4595
+ }
4596
+ // Fallback path: legacy fake-tool emulation, kept for models that the
4597
+ // runtime has cached as not supporting native `response_format`.
2862
4598
  const choice = openRouterResponse.choices[0];
2863
4599
  // SDK returns camelCase (toolCalls)
2864
4600
  if (!choice?.message?.toolCalls?.length) {
@@ -2870,6 +4606,20 @@ class OpenRouterAdapter extends BaseProviderAdapter {
2870
4606
  }
2871
4607
  extractStructuredOutput(response) {
2872
4608
  const openRouterResponse = response;
4609
+ if (openRouterResponse.__jaypieStructuredOutput) {
4610
+ const choice = openRouterResponse.choices[0];
4611
+ const content = choice?.message?.content;
4612
+ if (typeof content !== "string" || content.length === 0) {
4613
+ return undefined;
4614
+ }
4615
+ try {
4616
+ return JSON.parse(content);
4617
+ }
4618
+ catch {
4619
+ return undefined;
4620
+ }
4621
+ }
4622
+ // Fallback path: legacy fake-tool emulation
2873
4623
  const choice = openRouterResponse.choices[0];
2874
4624
  // SDK returns camelCase (toolCalls)
2875
4625
  if (!choice?.message?.toolCalls?.length) {
@@ -2995,8 +4745,54 @@ class OpenRouterAdapter extends BaseProviderAdapter {
2995
4745
  // Export singleton instance
2996
4746
  const openRouterAdapter = new OpenRouterAdapter();
2997
4747
 
4748
+ /**
4749
+ * Error-message substrings that indicate a transient xAI media-ingest flake.
4750
+ * The xAI ingest service enters a bad state after ~12 consecutive file-bearing
4751
+ * calls and rejects subsequent requests with HTTP 400. The condition is
4752
+ * self-clearing, so these errors should be retried with backoff rather than
4753
+ * surfaced as unrecoverable.
4754
+ *
4755
+ * See: github.com/finlaysonstudio/jaypie issue #301
4756
+ */
4757
+ const TRANSIENT_INGEST_MESSAGE_PATTERNS = [
4758
+ "failed to ingest inline file bytes",
4759
+ ];
4760
+ function isTransientIngestError(error) {
4761
+ if (!(error instanceof openai.BadRequestError))
4762
+ return false;
4763
+ const message = (error.message ?? "").toLowerCase();
4764
+ return TRANSIENT_INGEST_MESSAGE_PATTERNS.some((pattern) => message.includes(pattern));
4765
+ }
4766
+ /**
4767
+ * XaiAdapter extends OpenAiAdapter since xAI (Grok) uses an OpenAI-compatible API.
4768
+ * Name, default model, and transient-ingest-error detection are overridden;
4769
+ * all request building, response parsing, tool handling, and streaming are
4770
+ * inherited.
4771
+ */
4772
+ class XaiAdapter extends OpenAiAdapter {
4773
+ constructor() {
4774
+ super(...arguments);
4775
+ // @ts-expect-error Narrowing override: xAI name differs from parent's literal "openai"
4776
+ this.name = PROVIDER.XAI.NAME;
4777
+ // @ts-expect-error Narrowing override: xAI default model differs from parent's literal
4778
+ this.defaultModel = PROVIDER.XAI.MODEL.DEFAULT;
4779
+ }
4780
+ classifyError(error) {
4781
+ if (isTransientIngestError(error)) {
4782
+ return {
4783
+ error,
4784
+ category: ErrorCategory.Retryable,
4785
+ shouldRetry: true,
4786
+ };
4787
+ }
4788
+ return super.classifyError(error);
4789
+ }
4790
+ }
4791
+ // Export singleton instance
4792
+ const xaiAdapter = new XaiAdapter();
4793
+
2998
4794
  const DEFAULT_TOOL_TYPE = "function";
2999
- const log = log$2.log.lib({ lib: kit.JAYPIE.LIB.LLM });
4795
+ const log = log$1.log.lib({ lib: kit.JAYPIE.LIB.LLM });
3000
4796
  function logToolMessage(message, context) {
3001
4797
  log.trace.var({ [context.name]: message });
3002
4798
  }
@@ -3130,6 +4926,198 @@ class Toolkit {
3130
4926
  }
3131
4927
  }
3132
4928
 
4929
+ //
4930
+ //
4931
+ // Constants
4932
+ //
4933
+ const ENV = {
4934
+ DD_LLMOBS_ENABLED: "DD_LLMOBS_ENABLED",
4935
+ };
4936
+ const MODULE = {
4937
+ // Computed at runtime so bundlers (esbuild) do not attempt to include
4938
+ // dd-trace, which is provided by the Datadog Lambda layer at runtime.
4939
+ DD_TRACE: ["dd", "trace"].join("-"),
4940
+ };
4941
+ //
4942
+ //
4943
+ // Helpers
4944
+ //
4945
+ // CJS/ESM compatible require - handles bundling to CJS where import.meta.url
4946
+ // becomes undefined (mirrors packages/testkit/src/mock/original.ts).
4947
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
4948
+ // @ts-ignore - __filename exists in CJS context when ESM is bundled to CJS
4949
+ const requireModule = typeof __filename !== "undefined"
4950
+ ? module$1.createRequire(url.pathToFileURL(__filename).href)
4951
+ : module$1.createRequire((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href)));
4952
+ let resolved = false;
4953
+ let cachedSdk$4 = null;
4954
+ /**
4955
+ * Lazily resolve the dd-trace `llmobs` SDK. Returns null (and never throws)
4956
+ * when dd-trace is absent or the SDK surface is unexpected. Cached after the
4957
+ * first attempt.
4958
+ */
4959
+ function resolveLlmObs() {
4960
+ if (resolved) {
4961
+ return cachedSdk$4;
4962
+ }
4963
+ resolved = true;
4964
+ try {
4965
+ const ddTrace = requireModule(MODULE.DD_TRACE);
4966
+ const tracer = ddTrace?.default ?? ddTrace;
4967
+ const llmobs = tracer?.llmobs;
4968
+ if (llmobs &&
4969
+ typeof llmobs.trace === "function" &&
4970
+ typeof llmobs.annotate === "function") {
4971
+ cachedSdk$4 = llmobs;
4972
+ }
4973
+ }
4974
+ catch {
4975
+ cachedSdk$4 = null;
4976
+ }
4977
+ return cachedSdk$4;
4978
+ }
4979
+ //
4980
+ //
4981
+ // Main
4982
+ //
4983
+ /**
4984
+ * True when LLM Observability emission is opted in via `DD_LLMOBS_ENABLED`.
4985
+ * Accepts any truthy value except "false"/"0".
4986
+ */
4987
+ function isLlmObsEnabled() {
4988
+ const value = process.env[ENV.DD_LLMOBS_ENABLED];
4989
+ if (!value) {
4990
+ return false;
4991
+ }
4992
+ const normalized = value.trim().toLowerCase();
4993
+ return normalized !== "" && normalized !== "false" && normalized !== "0";
4994
+ }
4995
+ /** Resolve the SDK only when enabled and available. */
4996
+ function activeSdk() {
4997
+ if (!isLlmObsEnabled()) {
4998
+ return null;
4999
+ }
5000
+ return resolveLlmObs();
5001
+ }
5002
+ /**
5003
+ * Run `fn` inside an LLM Observability span. When emission is disabled or
5004
+ * dd-trace is absent, `fn` is invoked directly with no overhead. The active
5005
+ * span is established for the duration of `fn`, so nested `withLlmObsSpan`
5006
+ * calls (and `annotateLlmObs`) attach as children/annotations automatically.
5007
+ */
5008
+ async function withLlmObsSpan(options, fn) {
5009
+ const sdk = activeSdk();
5010
+ if (!sdk) {
5011
+ return fn();
5012
+ }
5013
+ let started = false;
5014
+ try {
5015
+ return (await sdk.trace(options, () => {
5016
+ started = true;
5017
+ return fn();
5018
+ }));
5019
+ }
5020
+ catch (error) {
5021
+ // If `fn` ran, propagate its error (the SDK re-throws after finishing the
5022
+ // span). Only swallow failures from the instrumentation itself, re-running
5023
+ // `fn` so observability never breaks the underlying call.
5024
+ if (started) {
5025
+ throw error;
5026
+ }
5027
+ getLogger$6().warn("[llmobs] span emission failed");
5028
+ getLogger$6().var({ error });
5029
+ return fn();
5030
+ }
5031
+ }
5032
+ /**
5033
+ * Annotate the currently active LLM Observability span. No-op when disabled.
5034
+ */
5035
+ function annotateLlmObs(annotation) {
5036
+ const sdk = activeSdk();
5037
+ if (!sdk) {
5038
+ return;
5039
+ }
5040
+ try {
5041
+ sdk.annotate(undefined, annotation);
5042
+ }
5043
+ catch (error) {
5044
+ getLogger$6().warn("[llmobs] annotate failed");
5045
+ getLogger$6().var({ error });
5046
+ }
5047
+ }
5048
+ /**
5049
+ * Open a manual span that is finished later. Used by streaming, where work is
5050
+ * yielded incrementally and cannot be enclosed in a single callback. The span
5051
+ * is kept open via a deferred promise resolved by `finish()`. Returns null when
5052
+ * emission is disabled or dd-trace is absent.
5053
+ *
5054
+ * Note: because the span is held open across `yield` boundaries (outside the
5055
+ * SDK's AsyncLocalStorage scope), manual spans are emitted flat rather than
5056
+ * nested. Annotations are applied to the explicit span reference.
5057
+ */
5058
+ function openLlmObsSpan(options) {
5059
+ const sdk = activeSdk();
5060
+ if (!sdk) {
5061
+ return null;
5062
+ }
5063
+ try {
5064
+ let capturedSpan;
5065
+ let release;
5066
+ const held = new Promise((resolve) => {
5067
+ release = resolve;
5068
+ });
5069
+ // trace() runs the callback synchronously, capturing the span; the returned
5070
+ // pending promise keeps the span open until finish() resolves it.
5071
+ void sdk.trace(options, (span) => {
5072
+ capturedSpan = span;
5073
+ return held;
5074
+ });
5075
+ let finished = false;
5076
+ return {
5077
+ annotate: (annotation) => {
5078
+ try {
5079
+ sdk.annotate(capturedSpan, annotation);
5080
+ }
5081
+ catch (error) {
5082
+ getLogger$6().warn("[llmobs] annotate failed");
5083
+ getLogger$6().var({ error });
5084
+ }
5085
+ },
5086
+ finish: () => {
5087
+ if (finished) {
5088
+ return;
5089
+ }
5090
+ finished = true;
5091
+ release?.();
5092
+ },
5093
+ };
5094
+ }
5095
+ catch (error) {
5096
+ getLogger$6().warn("[llmobs] span emission failed");
5097
+ getLogger$6().var({ error });
5098
+ return null;
5099
+ }
5100
+ }
5101
+ //
5102
+ //
5103
+ // Mapping Helpers
5104
+ //
5105
+ /**
5106
+ * Sum an `LlmUsage` array into LLM Observability metric keys. Returns undefined
5107
+ * when there is no usage data so callers can omit the `metrics` field.
5108
+ */
5109
+ function usageToLlmObsMetrics(usage) {
5110
+ if (!usage || usage.length === 0) {
5111
+ return undefined;
5112
+ }
5113
+ const metrics = usage.reduce((sum, item) => ({
5114
+ input_tokens: sum.input_tokens + (item.input ?? 0),
5115
+ output_tokens: sum.output_tokens + (item.output ?? 0),
5116
+ total_tokens: sum.total_tokens + (item.total ?? 0),
5117
+ }), { input_tokens: 0, output_tokens: 0, total_tokens: 0 });
5118
+ return metrics;
5119
+ }
5120
+
3133
5121
  //
3134
5122
  //
3135
5123
  // Main
@@ -3776,6 +5764,78 @@ class RetryPolicy {
3776
5764
  // Export a default policy instance
3777
5765
  const defaultRetryPolicy = new RetryPolicy();
3778
5766
 
5767
+ //
5768
+ //
5769
+ // Helpers
5770
+ //
5771
+ /**
5772
+ * Compare an unhandled rejection reason against errors the retry loop has
5773
+ * already caught. Reference equality first, then fall back to message + name
5774
+ * — providers sometimes surface twin rejections as fresh Error instances
5775
+ * rebuilt from the same upstream failure.
5776
+ */
5777
+ function matchesCaughtError(reason, caught) {
5778
+ if (caught.has(reason))
5779
+ return true;
5780
+ if (!(reason instanceof Error))
5781
+ return false;
5782
+ for (const handled of caught) {
5783
+ if (handled instanceof Error &&
5784
+ handled.name === reason.name &&
5785
+ handled.message === reason.message) {
5786
+ return true;
5787
+ }
5788
+ }
5789
+ return false;
5790
+ }
5791
+ //
5792
+ //
5793
+ // Main
5794
+ //
5795
+ /**
5796
+ * Create a guard that suppresses unhandled rejections firing as siblings of
5797
+ * an error the retry loop has already caught. Provider SDKs occasionally
5798
+ * surface a single upstream failure as twin rejections — the retry layer
5799
+ * accepts responsibility for the first; this guard prevents the second from
5800
+ * crashing the host while the retry is in flight.
5801
+ *
5802
+ * The guard also continues to suppress transient socket teardown errors
5803
+ * (e.g. undici `TypeError: terminated`) emitted between attempts.
5804
+ */
5805
+ function createStaleRejectionGuard() {
5806
+ const log = getLogger$6();
5807
+ const caughtErrors = new Set();
5808
+ let listener;
5809
+ return {
5810
+ install() {
5811
+ if (listener)
5812
+ return;
5813
+ listener = (reason, promise) => {
5814
+ if (isTransientNetworkError(reason)) {
5815
+ promise?.catch?.(() => { });
5816
+ log.trace("Suppressed stale socket error during retry");
5817
+ return;
5818
+ }
5819
+ if (matchesCaughtError(reason, caughtErrors)) {
5820
+ promise?.catch?.(() => { });
5821
+ log.trace("Suppressed sibling rejection of already-handled error");
5822
+ }
5823
+ };
5824
+ process.on("unhandledRejection", listener);
5825
+ },
5826
+ remove() {
5827
+ if (listener) {
5828
+ process.removeListener("unhandledRejection", listener);
5829
+ listener = undefined;
5830
+ }
5831
+ caughtErrors.clear();
5832
+ },
5833
+ recordCaught(error) {
5834
+ caughtErrors.add(error);
5835
+ },
5836
+ };
5837
+ }
5838
+
3779
5839
  //
3780
5840
  //
3781
5841
  // Main
@@ -3791,67 +5851,85 @@ class RetryExecutor {
3791
5851
  this.errorClassifier = config.errorClassifier;
3792
5852
  }
3793
5853
  /**
3794
- * Execute an operation with retry logic
5854
+ * Execute an operation with retry logic.
5855
+ * Each attempt receives an AbortSignal. On failure, the signal is aborted
5856
+ * before sleeping — this kills lingering socket callbacks from the previous
5857
+ * request and prevents stale async errors from escaping the retry loop.
3795
5858
  *
3796
- * @param operation - The async operation to execute
5859
+ * @param operation - The async operation to execute (receives AbortSignal)
3797
5860
  * @param options - Execution options including context and hooks
3798
5861
  * @returns The result of the operation
3799
5862
  * @throws BadGatewayError if all retries are exhausted or error is not retryable
3800
5863
  */
3801
5864
  async execute(operation, options) {
5865
+ const log = getLogger$6();
3802
5866
  let attempt = 0;
3803
- while (true) {
3804
- try {
3805
- const result = await operation();
3806
- if (attempt > 0) {
3807
- log$1.debug(`API call succeeded after ${attempt} retries`);
3808
- }
3809
- return result;
3810
- }
3811
- catch (error) {
3812
- // Check if we've exhausted retries
3813
- if (!this.policy.shouldRetry(attempt)) {
3814
- log$1.error(`API call failed after ${this.policy.maxRetries} retries`);
3815
- log$1.var({ error });
3816
- await this.hookRunner.runOnUnrecoverableError(options.hooks, {
3817
- input: options.context.input,
3818
- options: options.context.options,
3819
- providerRequest: options.context.providerRequest,
3820
- error,
3821
- });
3822
- const errorMessage = error instanceof Error ? error.message : String(error);
3823
- throw new errors.BadGatewayError(errorMessage);
5867
+ // Guard against stale rejections firing on a subsequent microtask after
5868
+ // the retry layer has already caught the originating error: undici socket
5869
+ // teardown (TypeError: terminated) and twin upstream-SDK rejections
5870
+ // (e.g. issue #336 — OpenRouter SyntaxError siblings).
5871
+ const guard = createStaleRejectionGuard();
5872
+ try {
5873
+ while (true) {
5874
+ const controller = new AbortController();
5875
+ try {
5876
+ const result = await operation(controller.signal);
5877
+ if (attempt > 0) {
5878
+ log.debug(`API call succeeded after ${attempt} retries`);
5879
+ }
5880
+ return result;
3824
5881
  }
3825
- // Check if error is not retryable
3826
- if (!this.errorClassifier.isRetryable(error)) {
3827
- log$1.error("API call failed with non-retryable error");
3828
- log$1.var({ error });
3829
- await this.hookRunner.runOnUnrecoverableError(options.hooks, {
5882
+ catch (error) {
5883
+ controller.abort("retry");
5884
+ guard.recordCaught(error);
5885
+ guard.install();
5886
+ // Check if we've exhausted retries
5887
+ if (!this.policy.shouldRetry(attempt)) {
5888
+ log.error(`API call failed after ${this.policy.maxRetries} retries`);
5889
+ log.var({ error });
5890
+ await this.hookRunner.runOnUnrecoverableError(options.hooks, {
5891
+ input: options.context.input,
5892
+ options: options.context.options,
5893
+ providerRequest: options.context.providerRequest,
5894
+ error,
5895
+ });
5896
+ const errorMessage = error instanceof Error ? error.message : String(error);
5897
+ throw new errors.BadGatewayError(errorMessage);
5898
+ }
5899
+ // Check if error is not retryable
5900
+ if (!this.errorClassifier.isRetryable(error)) {
5901
+ log.error("API call failed with non-retryable error");
5902
+ log.var({ error });
5903
+ await this.hookRunner.runOnUnrecoverableError(options.hooks, {
5904
+ input: options.context.input,
5905
+ options: options.context.options,
5906
+ providerRequest: options.context.providerRequest,
5907
+ error,
5908
+ });
5909
+ const errorMessage = error instanceof Error ? error.message : String(error);
5910
+ throw new errors.BadGatewayError(errorMessage);
5911
+ }
5912
+ // Warn if this is an unknown error type
5913
+ if (!this.errorClassifier.isKnownError(error)) {
5914
+ log.warn("API returned unknown error type, will retry");
5915
+ log.var({ error });
5916
+ }
5917
+ const delay = this.policy.getDelayForAttempt(attempt);
5918
+ log.warn(`API call failed. Retrying in ${delay}ms...`);
5919
+ await this.hookRunner.runOnRetryableError(options.hooks, {
3830
5920
  input: options.context.input,
3831
5921
  options: options.context.options,
3832
5922
  providerRequest: options.context.providerRequest,
3833
5923
  error,
3834
5924
  });
3835
- const errorMessage = error instanceof Error ? error.message : String(error);
3836
- throw new errors.BadGatewayError(errorMessage);
3837
- }
3838
- // Warn if this is an unknown error type
3839
- if (!this.errorClassifier.isKnownError(error)) {
3840
- log$1.warn("API returned unknown error type, will retry");
3841
- log$1.var({ error });
5925
+ await kit.sleep(delay);
5926
+ attempt++;
3842
5927
  }
3843
- const delay = this.policy.getDelayForAttempt(attempt);
3844
- log$1.warn(`API call failed. Retrying in ${delay}ms...`);
3845
- await this.hookRunner.runOnRetryableError(options.hooks, {
3846
- input: options.context.input,
3847
- options: options.context.options,
3848
- providerRequest: options.context.providerRequest,
3849
- error,
3850
- });
3851
- await kit.sleep(delay);
3852
- attempt++;
3853
5928
  }
3854
5929
  }
5930
+ finally {
5931
+ guard.remove();
5932
+ }
3855
5933
  }
3856
5934
  }
3857
5935
 
@@ -3862,6 +5940,7 @@ class RetryExecutor {
3862
5940
  const ERROR$1 = {
3863
5941
  BAD_FUNCTION_CALL: "Bad Function Call",
3864
5942
  };
5943
+ const MAX_CONSECUTIVE_TOOL_ERRORS = 6;
3865
5944
  //
3866
5945
  //
3867
5946
  // Helpers
@@ -3902,37 +5981,54 @@ class OperateLoop {
3902
5981
  * Execute the operate loop for multi-turn conversations with tool calling.
3903
5982
  */
3904
5983
  async execute(input, options = {}) {
5984
+ const log = getLogger$6();
3905
5985
  // Log what was passed to operate
3906
- log$1.trace("[operate] Starting operate loop");
3907
- log$1.var({ "operate.input": input });
3908
- log$1.var({ "operate.options": options });
5986
+ log.trace("[operate] Starting operate loop");
5987
+ log.var({ "operate.input": input });
5988
+ log.var({ "operate.options": options });
3909
5989
  // Initialize state
3910
5990
  const state = await this.initializeState(input, options);
3911
5991
  const context = this.createContext(options);
3912
- // Build initial request
3913
- let request = this.buildInitialRequest(state, options);
3914
- // Multi-turn loop
3915
- while (state.currentTurn < state.maxTurns) {
3916
- state.currentTurn++;
3917
- // Execute one turn with retry logic
3918
- const shouldContinue = await this.executeOneTurn(request, state, context, options);
3919
- if (!shouldContinue) {
3920
- break;
5992
+ const modelName = options.model ?? this.adapter.defaultModel;
5993
+ // Enclosing LLM Observability span (no-op when DD_LLMOBS_ENABLED is unset).
5994
+ // Child llm/tool spans nest under it via the SDK's active-span context.
5995
+ return withLlmObsSpan({
5996
+ kind: state.toolkit ? "agent" : "llm",
5997
+ modelName,
5998
+ modelProvider: this.adapter.name,
5999
+ name: "jaypie.llm.operate",
6000
+ }, async () => {
6001
+ // Build initial request
6002
+ let request = this.buildInitialRequest(state, options);
6003
+ // Multi-turn loop
6004
+ while (state.currentTurn < state.maxTurns) {
6005
+ state.currentTurn++;
6006
+ // Execute one turn with retry logic
6007
+ const shouldContinue = await this.executeOneTurn(request, state, context, options);
6008
+ if (!shouldContinue) {
6009
+ break;
6010
+ }
6011
+ // Rebuild request with updated history for next turn
6012
+ request = {
6013
+ format: state.formattedFormat,
6014
+ instructions: options.instructions,
6015
+ messages: state.currentInput,
6016
+ model: modelName,
6017
+ providerOptions: options.providerOptions,
6018
+ system: options.system,
6019
+ temperature: options.temperature,
6020
+ tools: state.formattedTools,
6021
+ user: options.user,
6022
+ };
3921
6023
  }
3922
- // Rebuild request with updated history for next turn
3923
- request = {
3924
- format: state.formattedFormat,
3925
- instructions: options.instructions,
3926
- messages: state.currentInput,
3927
- model: options.model ?? this.adapter.defaultModel,
3928
- providerOptions: options.providerOptions,
3929
- system: options.system,
3930
- temperature: options.temperature,
3931
- tools: state.formattedTools,
3932
- user: options.user,
3933
- };
3934
- }
3935
- return state.responseBuilder.build();
6024
+ const response = state.responseBuilder.build();
6025
+ annotateLlmObs({
6026
+ inputData: input,
6027
+ metrics: usageToLlmObsMetrics(response.usage),
6028
+ outputData: response.content,
6029
+ });
6030
+ return response;
6031
+ });
3936
6032
  }
3937
6033
  //
3938
6034
  // Private Methods
@@ -3984,6 +6080,7 @@ class OperateLoop {
3984
6080
  }
3985
6081
  }
3986
6082
  return {
6083
+ consecutiveToolErrors: 0,
3987
6084
  currentInput: processedInput.history,
3988
6085
  currentTurn: 0,
3989
6086
  formattedFormat,
@@ -4013,6 +6110,7 @@ class OperateLoop {
4013
6110
  };
4014
6111
  }
4015
6112
  async executeOneTurn(request, state, context, options) {
6113
+ const log = getLogger$6();
4016
6114
  // Create error classifier from adapter
4017
6115
  const errorClassifier = createErrorClassifier(this.adapter);
4018
6116
  // Create retry executor for this turn
@@ -4024,28 +6122,42 @@ class OperateLoop {
4024
6122
  // Build provider-specific request
4025
6123
  const providerRequest = this.adapter.buildRequest(request);
4026
6124
  // Log what was passed to the model
4027
- log$1.trace("[operate] Calling model");
4028
- log$1.var({ "operate.request": providerRequest });
6125
+ log.trace("[operate] Calling model");
6126
+ log.var({ "operate.request": providerRequest });
4029
6127
  // Execute beforeEachModelRequest hook
4030
6128
  await this.hookRunnerInstance.runBeforeModelRequest(context.hooks, {
4031
6129
  input: state.currentInput,
4032
6130
  options,
4033
6131
  providerRequest,
4034
6132
  });
4035
- // Execute with retry (RetryExecutor handles error hooks and throws appropriate errors)
4036
- const response = await retryExecutor.execute(() => this.adapter.executeRequest(this.client, providerRequest), {
4037
- context: {
4038
- input: state.currentInput,
4039
- options,
4040
- providerRequest,
4041
- },
4042
- hooks: context.hooks,
6133
+ // Execute with retry inside a child llm span (no-op when llmobs disabled).
6134
+ // RetryExecutor handles error hooks and throws appropriate errors.
6135
+ const { parsed, response } = await withLlmObsSpan({
6136
+ kind: "llm",
6137
+ modelName: options.model ?? this.adapter.defaultModel,
6138
+ modelProvider: this.adapter.name,
6139
+ name: "jaypie.llm.model",
6140
+ }, async () => {
6141
+ const response = await retryExecutor.execute((signal) => this.adapter.executeRequest(this.client, providerRequest, signal), {
6142
+ context: {
6143
+ input: state.currentInput,
6144
+ options,
6145
+ providerRequest,
6146
+ },
6147
+ hooks: context.hooks,
6148
+ });
6149
+ // Log what was returned from the model
6150
+ log.trace("[operate] Model response received");
6151
+ log.var({ "operate.response": response });
6152
+ // Parse response
6153
+ const parsed = this.adapter.parseResponse(response, options);
6154
+ annotateLlmObs({
6155
+ inputData: state.currentInput,
6156
+ metrics: usageToLlmObsMetrics(parsed.usage ? [parsed.usage] : undefined),
6157
+ outputData: parsed.content ?? "",
6158
+ });
6159
+ return { parsed, response };
4043
6160
  });
4044
- // Log what was returned from the model
4045
- log$1.trace("[operate] Model response received");
4046
- log$1.var({ "operate.response": response });
4047
- // Parse response
4048
- const parsed = this.adapter.parseResponse(response, options);
4049
6161
  // Track usage
4050
6162
  if (parsed.usage) {
4051
6163
  state.responseBuilder.addUsage(parsed.usage);
@@ -4066,7 +6178,7 @@ class OperateLoop {
4066
6178
  if (this.adapter.hasStructuredOutput(response)) {
4067
6179
  const structuredOutput = this.adapter.extractStructuredOutput(response);
4068
6180
  if (structuredOutput) {
4069
- state.responseBuilder.setContent(structuredOutput);
6181
+ state.responseBuilder.setContent(this.applyFormatArrayDefaults(structuredOutput, options));
4070
6182
  state.responseBuilder.complete();
4071
6183
  return false; // Stop loop
4072
6184
  }
@@ -4090,11 +6202,19 @@ class OperateLoop {
4090
6202
  args: toolCall.arguments,
4091
6203
  toolName: toolCall.name,
4092
6204
  });
4093
- // Call the tool
4094
- log$1.trace(`[operate] Calling tool - ${toolCall.name}`);
4095
- const result = await state.toolkit.call({
4096
- arguments: toolCall.arguments,
4097
- name: toolCall.name,
6205
+ // Call the tool inside a child tool span (no-op when disabled)
6206
+ log.trace(`[operate] Calling tool - ${toolCall.name}`);
6207
+ const result = await withLlmObsSpan({ kind: "tool", name: toolCall.name }, async () => {
6208
+ const result = await state.toolkit.call({
6209
+ arguments: toolCall.arguments,
6210
+ name: toolCall.name,
6211
+ });
6212
+ annotateLlmObs({
6213
+ inputData: toolCall.arguments,
6214
+ metadata: { tool: toolCall.name },
6215
+ outputData: result,
6216
+ });
6217
+ return result;
4098
6218
  });
4099
6219
  // Execute afterEachTool hook
4100
6220
  await this.hookRunnerInstance.runAfterTool(context.hooks, {
@@ -4108,6 +6228,8 @@ class OperateLoop {
4108
6228
  output: JSON.stringify(result),
4109
6229
  success: true,
4110
6230
  };
6231
+ // Reset consecutive error counter on success
6232
+ state.consecutiveToolErrors = 0;
4111
6233
  // Update provider request with tool result
4112
6234
  currentProviderRequest = this.adapter.appendToolResult(currentProviderRequest, toolCall, formattedResult);
4113
6235
  // Sync state from updated request
@@ -4134,15 +6256,41 @@ class OperateLoop {
4134
6256
  status: jaypieError.status,
4135
6257
  title: ERROR$1.BAD_FUNCTION_CALL,
4136
6258
  });
4137
- log$1.error(`Error executing function call ${toolCall.name}`);
4138
- log$1.var({ error });
6259
+ // Add error tool_result to history so the tool_use block is not orphaned.
6260
+ // Without this, the next turn's request would have a tool_use without a
6261
+ // matching tool_result, causing Anthropic API to reject with 400.
6262
+ const errorResult = {
6263
+ callId: toolCall.callId,
6264
+ output: JSON.stringify({
6265
+ error: error.message || "Tool execution failed",
6266
+ }),
6267
+ success: false,
6268
+ error: error.message,
6269
+ };
6270
+ const toolResultFormatted = this.adapter.formatToolResult(toolCall, errorResult);
6271
+ state.responseBuilder.appendToHistory(toolResultFormatted);
6272
+ log.warn(`Error executing function call ${toolCall.name}`);
6273
+ log.var({ error });
6274
+ // Track consecutive errors and stop if threshold reached
6275
+ state.consecutiveToolErrors++;
6276
+ if (state.consecutiveToolErrors >= MAX_CONSECUTIVE_TOOL_ERRORS) {
6277
+ const detail = `Stopped after ${MAX_CONSECUTIVE_TOOL_ERRORS} consecutive tool errors`;
6278
+ log.warn(detail);
6279
+ state.responseBuilder.setError({
6280
+ detail,
6281
+ status: 502,
6282
+ title: ERROR$1.BAD_FUNCTION_CALL,
6283
+ });
6284
+ state.responseBuilder.incomplete();
6285
+ return false; // Stop loop
6286
+ }
4139
6287
  }
4140
6288
  }
4141
6289
  // Check if we've reached max turns
4142
6290
  if (state.currentTurn >= state.maxTurns) {
4143
6291
  const error = new errors.TooManyRequestsError();
4144
6292
  const detail = `Model requested function call but exceeded ${state.maxTurns} turns`;
4145
- log$1.warn(detail);
6293
+ log.warn(detail);
4146
6294
  state.responseBuilder.setError({
4147
6295
  detail,
4148
6296
  status: error.status,
@@ -4155,7 +6303,7 @@ class OperateLoop {
4155
6303
  }
4156
6304
  }
4157
6305
  // No tool calls or no toolkit - we're done
4158
- state.responseBuilder.setContent(parsed.content);
6306
+ state.responseBuilder.setContent(this.applyFormatArrayDefaults(parsed.content, options));
4159
6307
  state.responseBuilder.complete();
4160
6308
  // Add final history items
4161
6309
  const historyItems = this.adapter.responseToHistoryItems(parsed.raw);
@@ -4164,6 +6312,20 @@ class OperateLoop {
4164
6312
  }
4165
6313
  return false; // Stop loop
4166
6314
  }
6315
+ /**
6316
+ * Backfill declared array fields when a `format` is supplied. A declared
6317
+ * `format` is a schema contract: an empty array field should surface as `[]`
6318
+ * rather than be dropped by a provider/model that omits empty lists.
6319
+ */
6320
+ applyFormatArrayDefaults(content, options) {
6321
+ if (!options.format) {
6322
+ return content;
6323
+ }
6324
+ if (typeof content !== "object" || content === null) {
6325
+ return content;
6326
+ }
6327
+ return fillFormatArrays({ content, format: options.format });
6328
+ }
4167
6329
  /**
4168
6330
  * Sync the current input state from the updated provider request.
4169
6331
  * This is necessary because appendToolResult modifies the provider-specific request,
@@ -4289,12 +6451,14 @@ class StreamLoop {
4289
6451
  this.client = config.client;
4290
6452
  this.hookRunnerInstance = config.hookRunner ?? hookRunner;
4291
6453
  this.inputProcessorInstance = config.inputProcessor ?? inputProcessor;
6454
+ this.retryPolicy = config.retryPolicy ?? defaultRetryPolicy;
4292
6455
  }
4293
6456
  /**
4294
6457
  * Execute the streaming loop for multi-turn conversations with tool calling.
4295
6458
  * Yields stream chunks as they become available.
4296
6459
  */
4297
6460
  async *execute(input, options = {}) {
6461
+ const log = getLogger$6();
4298
6462
  // Verify adapter supports streaming
4299
6463
  if (!this.adapter.executeStreamRequest) {
4300
6464
  throw new errors.BadGatewayError(`Provider ${this.adapter.name} does not support streaming`);
@@ -4314,12 +6478,12 @@ class StreamLoop {
4314
6478
  }
4315
6479
  // If we have tool calls, process them
4316
6480
  if (toolCalls && toolCalls.length > 0 && state.toolkit) {
4317
- yield* this.processToolCalls(toolCalls, state, context, options);
6481
+ yield* this.processToolCalls(toolCalls, state, context);
4318
6482
  // Check if we've reached max turns
4319
6483
  if (state.currentTurn >= state.maxTurns) {
4320
6484
  const error = new errors.TooManyRequestsError();
4321
6485
  const detail = `Model requested function call but exceeded ${state.maxTurns} turns`;
4322
- log$1.warn(detail);
6486
+ log.warn(detail);
4323
6487
  yield {
4324
6488
  type: exports.LlmStreamChunkType.Error,
4325
6489
  error: {
@@ -4382,6 +6546,7 @@ class StreamLoop {
4382
6546
  ? this.adapter.formatTools(toolkit, formattedFormat)
4383
6547
  : undefined;
4384
6548
  return {
6549
+ consecutiveToolErrors: 0,
4385
6550
  currentInput: processedInput.history,
4386
6551
  currentTurn: 0,
4387
6552
  formattedFormat,
@@ -4411,6 +6576,7 @@ class StreamLoop {
4411
6576
  };
4412
6577
  }
4413
6578
  async *executeOneStreamingTurn(request, state, context, options) {
6579
+ const log = getLogger$6();
4414
6580
  // Build provider-specific request
4415
6581
  const providerRequest = this.adapter.buildRequest(request);
4416
6582
  // Execute beforeEachModelRequest hook
@@ -4419,34 +6585,117 @@ class StreamLoop {
4419
6585
  options,
4420
6586
  providerRequest,
4421
6587
  });
6588
+ // Open a manual llm span held open across the streamed turn. Flat (not
6589
+ // nested) because it spans yield boundaries; no-op when llmobs disabled.
6590
+ const llmSpan = openLlmObsSpan({
6591
+ kind: "llm",
6592
+ modelName: options.model ?? this.adapter.defaultModel,
6593
+ modelProvider: this.adapter.name,
6594
+ name: "jaypie.llm.model",
6595
+ });
6596
+ const inputSnapshot = [...state.currentInput];
6597
+ let streamedText = "";
4422
6598
  // Collect tool calls from the stream
4423
6599
  const collectedToolCalls = [];
4424
- // Execute streaming request
4425
- const streamGenerator = this.adapter.executeStreamRequest(this.client, providerRequest);
4426
- for await (const chunk of streamGenerator) {
4427
- // Pass through text chunks
4428
- if (chunk.type === exports.LlmStreamChunkType.Text) {
4429
- yield chunk;
4430
- }
4431
- // Collect tool calls
4432
- if (chunk.type === exports.LlmStreamChunkType.ToolCall) {
4433
- collectedToolCalls.push({
4434
- callId: chunk.toolCall.id,
4435
- name: chunk.toolCall.name,
4436
- arguments: chunk.toolCall.arguments,
4437
- raw: chunk.toolCall,
4438
- });
4439
- yield chunk;
4440
- }
4441
- // Track usage from done chunk (but don't yield it yet - we'll emit our own)
4442
- if (chunk.type === exports.LlmStreamChunkType.Done && chunk.usage) {
4443
- state.usageItems.push(...chunk.usage);
4444
- }
4445
- // Pass through error chunks
4446
- if (chunk.type === exports.LlmStreamChunkType.Error) {
4447
- yield chunk;
6600
+ // Retry loop for connection-level failures
6601
+ let attempt = 0;
6602
+ let chunksYielded = false;
6603
+ // Guard against stale rejections firing after the stream loop has already
6604
+ // caught the originating error: undici socket teardown and twin
6605
+ // upstream-SDK rejections (issue #336).
6606
+ const guard = createStaleRejectionGuard();
6607
+ try {
6608
+ while (true) {
6609
+ const controller = new AbortController();
6610
+ try {
6611
+ // Execute streaming request
6612
+ const streamGenerator = this.adapter.executeStreamRequest(this.client, providerRequest, controller.signal);
6613
+ for await (const chunk of streamGenerator) {
6614
+ // Pass through text chunks
6615
+ if (chunk.type === exports.LlmStreamChunkType.Text) {
6616
+ chunksYielded = true;
6617
+ streamedText += chunk.content ?? "";
6618
+ yield chunk;
6619
+ }
6620
+ // Collect tool calls
6621
+ if (chunk.type === exports.LlmStreamChunkType.ToolCall) {
6622
+ chunksYielded = true;
6623
+ collectedToolCalls.push({
6624
+ callId: chunk.toolCall.id,
6625
+ name: chunk.toolCall.name,
6626
+ arguments: chunk.toolCall.arguments,
6627
+ raw: chunk.toolCall,
6628
+ });
6629
+ yield chunk;
6630
+ }
6631
+ // Track usage from done chunk (but don't yield it yet - we'll emit our own)
6632
+ if (chunk.type === exports.LlmStreamChunkType.Done && chunk.usage) {
6633
+ state.usageItems.push(...chunk.usage);
6634
+ }
6635
+ // Pass through error chunks
6636
+ if (chunk.type === exports.LlmStreamChunkType.Error) {
6637
+ chunksYielded = true;
6638
+ yield chunk;
6639
+ }
6640
+ }
6641
+ // Stream completed successfully
6642
+ if (attempt > 0) {
6643
+ log.debug(`Stream request succeeded after ${attempt} retries`);
6644
+ }
6645
+ break;
6646
+ }
6647
+ catch (error) {
6648
+ controller.abort("retry");
6649
+ guard.recordCaught(error);
6650
+ guard.install();
6651
+ // If chunks were already yielded, we can't transparently retry
6652
+ if (chunksYielded) {
6653
+ const errorMessage = error instanceof Error ? error.message : String(error);
6654
+ log.error("Stream failed after partial data was delivered");
6655
+ log.var({ error });
6656
+ yield {
6657
+ type: exports.LlmStreamChunkType.Error,
6658
+ error: {
6659
+ detail: errorMessage,
6660
+ status: 502,
6661
+ title: "Stream Error",
6662
+ },
6663
+ };
6664
+ llmSpan?.annotate({
6665
+ inputData: inputSnapshot,
6666
+ metrics: usageToLlmObsMetrics(state.usageItems),
6667
+ outputData: streamedText,
6668
+ });
6669
+ llmSpan?.finish();
6670
+ return { shouldContinue: false };
6671
+ }
6672
+ // Check if we've exhausted retries or error is not retryable
6673
+ if (!this.retryPolicy.shouldRetry(attempt) ||
6674
+ !this.adapter.isRetryableError(error)) {
6675
+ log.error(`Stream request failed after ${this.retryPolicy.maxRetries} retries`);
6676
+ log.var({ error });
6677
+ const errorMessage = error instanceof Error ? error.message : String(error);
6678
+ llmSpan?.finish();
6679
+ throw new errors.BadGatewayError(errorMessage);
6680
+ }
6681
+ const delay = this.retryPolicy.getDelayForAttempt(attempt);
6682
+ log.warn(`Stream request failed. Retrying in ${delay}ms...`);
6683
+ log.var({ error });
6684
+ await kit.sleep(delay);
6685
+ attempt++;
6686
+ }
4448
6687
  }
4449
6688
  }
6689
+ finally {
6690
+ guard.remove();
6691
+ }
6692
+ // Annotate and finish the streamed llm span (no-op when disabled)
6693
+ llmSpan?.annotate({
6694
+ inputData: inputSnapshot,
6695
+ metrics: usageToLlmObsMetrics(state.usageItems),
6696
+ outputData: streamedText,
6697
+ });
6698
+ llmSpan?.finish();
4450
6699
  // Execute afterEachModelResponse hook
4451
6700
  await this.hookRunnerInstance.runAfterModelResponse(context.hooks, {
4452
6701
  content: "",
@@ -4460,19 +6709,29 @@ class StreamLoop {
4460
6709
  if (collectedToolCalls.length > 0 && state.toolkit && state.maxTurns > 1) {
4461
6710
  // Add tool calls to history
4462
6711
  for (const toolCall of collectedToolCalls) {
4463
- state.currentInput.push({
6712
+ // Extract provider-specific metadata from the stream chunk
6713
+ const metadata = toolCall.raw?.metadata;
6714
+ const historyItem = {
4464
6715
  type: exports.LlmMessageType.FunctionCall,
4465
6716
  name: toolCall.name,
4466
6717
  arguments: toolCall.arguments,
4467
6718
  call_id: toolCall.callId,
4468
- id: toolCall.callId,
4469
- });
6719
+ // Use provider item ID if available (e.g., OpenAI fc_... prefix),
6720
+ // otherwise fall back to callId
6721
+ id: metadata?.itemId || toolCall.callId,
6722
+ };
6723
+ // Preserve provider-specific fields (e.g., Gemini thoughtSignature)
6724
+ if (metadata?.thoughtSignature) {
6725
+ historyItem.thoughtSignature = metadata.thoughtSignature;
6726
+ }
6727
+ state.currentInput.push(historyItem);
4470
6728
  }
4471
6729
  return { shouldContinue: true, toolCalls: collectedToolCalls };
4472
6730
  }
4473
6731
  return { shouldContinue: false };
4474
6732
  }
4475
- async *processToolCalls(toolCalls, state, context, _options) {
6733
+ async *processToolCalls(toolCalls, state, context) {
6734
+ const log = getLogger$6();
4476
6735
  for (const toolCall of toolCalls) {
4477
6736
  try {
4478
6737
  // Execute beforeEachTool hook
@@ -4480,11 +6739,19 @@ class StreamLoop {
4480
6739
  args: toolCall.arguments,
4481
6740
  toolName: toolCall.name,
4482
6741
  });
4483
- // Call the tool
4484
- log$1.trace(`[stream] Calling tool - ${toolCall.name}`);
4485
- const result = await state.toolkit.call({
4486
- arguments: toolCall.arguments,
4487
- name: toolCall.name,
6742
+ // Call the tool inside a child tool span (no-op when disabled)
6743
+ log.trace(`[stream] Calling tool - ${toolCall.name}`);
6744
+ const result = await withLlmObsSpan({ kind: "tool", name: toolCall.name }, async () => {
6745
+ const result = await state.toolkit.call({
6746
+ arguments: toolCall.arguments,
6747
+ name: toolCall.name,
6748
+ });
6749
+ annotateLlmObs({
6750
+ inputData: toolCall.arguments,
6751
+ metadata: { tool: toolCall.name },
6752
+ outputData: result,
6753
+ });
6754
+ return result;
4488
6755
  });
4489
6756
  // Execute afterEachTool hook
4490
6757
  await this.hookRunnerInstance.runAfterTool(context.hooks, {
@@ -4492,6 +6759,8 @@ class StreamLoop {
4492
6759
  result,
4493
6760
  toolName: toolCall.name,
4494
6761
  });
6762
+ // Reset consecutive error counter on success
6763
+ state.consecutiveToolErrors = 0;
4495
6764
  // Yield tool result chunk
4496
6765
  yield {
4497
6766
  type: exports.LlmStreamChunkType.ToolResult,
@@ -4530,8 +6799,35 @@ class StreamLoop {
4530
6799
  title: ERROR.BAD_FUNCTION_CALL,
4531
6800
  },
4532
6801
  };
4533
- log$1.error(`Error executing function call ${toolCall.name}`);
4534
- log$1.var({ error });
6802
+ // Add error tool_result to history so the tool_use block is not orphaned.
6803
+ // Without this, the next turn's request would have a tool_use without a
6804
+ // matching tool_result, causing Anthropic API to reject with 400.
6805
+ const errorOutput = JSON.stringify({
6806
+ error: error.message || "Tool execution failed",
6807
+ });
6808
+ state.currentInput.push({
6809
+ type: exports.LlmMessageType.FunctionCallOutput,
6810
+ output: errorOutput,
6811
+ call_id: toolCall.callId,
6812
+ name: toolCall.name,
6813
+ });
6814
+ log.warn(`Error executing function call ${toolCall.name}`);
6815
+ log.var({ error });
6816
+ // Track consecutive errors and stop if threshold reached
6817
+ state.consecutiveToolErrors++;
6818
+ if (state.consecutiveToolErrors >= MAX_CONSECUTIVE_TOOL_ERRORS) {
6819
+ const stopDetail = `Stopped after ${MAX_CONSECUTIVE_TOOL_ERRORS} consecutive tool errors`;
6820
+ log.warn(stopDetail);
6821
+ yield {
6822
+ type: exports.LlmStreamChunkType.Error,
6823
+ error: {
6824
+ detail: stopDetail,
6825
+ status: 502,
6826
+ title: ERROR.BAD_FUNCTION_CALL,
6827
+ },
6828
+ };
6829
+ return; // Stop processing tools
6830
+ }
4535
6831
  }
4536
6832
  }
4537
6833
  }
@@ -4589,28 +6885,28 @@ function createStreamLoop(config) {
4589
6885
  }
4590
6886
 
4591
6887
  // SDK loader with caching
4592
- let cachedSdk$2 = null;
4593
- async function loadSdk$2() {
4594
- if (cachedSdk$2)
4595
- return cachedSdk$2;
6888
+ let cachedSdk$3 = null;
6889
+ async function loadSdk$3() {
6890
+ if (cachedSdk$3)
6891
+ return cachedSdk$3;
4596
6892
  try {
4597
- cachedSdk$2 = await import('@anthropic-ai/sdk');
4598
- return cachedSdk$2;
6893
+ cachedSdk$3 = await import('@anthropic-ai/sdk');
6894
+ return cachedSdk$3;
4599
6895
  }
4600
6896
  catch {
4601
6897
  throw new errors.ConfigurationError("@anthropic-ai/sdk is required but not installed. Run: npm install @anthropic-ai/sdk");
4602
6898
  }
4603
6899
  }
4604
6900
  // Logger
4605
- const getLogger$3 = () => log$2.log.lib({ lib: kit.JAYPIE.LIB.LLM });
6901
+ const getLogger$5 = () => log$1.log.lib({ lib: kit.JAYPIE.LIB.LLM });
4606
6902
  // Client initialization
4607
- async function initializeClient$3({ apiKey, } = {}) {
4608
- const logger = getLogger$3();
6903
+ async function initializeClient$5({ apiKey, } = {}) {
6904
+ const logger = getLogger$5();
4609
6905
  const resolvedApiKey = apiKey || (await aws.getEnvSecret("ANTHROPIC_API_KEY"));
4610
6906
  if (!resolvedApiKey) {
4611
6907
  throw new errors.ConfigurationError("The application could not resolve the required API key: ANTHROPIC_API_KEY");
4612
6908
  }
4613
- const sdk = await loadSdk$2();
6909
+ const sdk = await loadSdk$3();
4614
6910
  const client = new sdk.default({ apiKey: resolvedApiKey });
4615
6911
  logger.trace("Initialized Anthropic client");
4616
6912
  return client;
@@ -4631,7 +6927,7 @@ function formatUserMessage$3(message, { data, placeholders } = {}) {
4631
6927
  };
4632
6928
  }
4633
6929
  function prepareMessages$3(message, { data, placeholders } = {}) {
4634
- const logger = getLogger$3();
6930
+ const logger = getLogger$5();
4635
6931
  const messages = [];
4636
6932
  // Add user message (necessary for all requests)
4637
6933
  const userMessage = formatUserMessage$3(message, { data, placeholders });
@@ -4641,7 +6937,7 @@ function prepareMessages$3(message, { data, placeholders } = {}) {
4641
6937
  }
4642
6938
  // Basic text completion
4643
6939
  async function createTextCompletion$1(client, messages, model, systemMessage) {
4644
- log$2.log.trace("Using text output (unstructured)");
6940
+ log$1.log.trace("Using text output (unstructured)");
4645
6941
  const params = {
4646
6942
  model,
4647
6943
  messages,
@@ -4650,63 +6946,58 @@ async function createTextCompletion$1(client, messages, model, systemMessage) {
4650
6946
  // Add system instruction if provided
4651
6947
  if (systemMessage) {
4652
6948
  params.system = systemMessage;
4653
- log$2.log.trace(`System message: ${systemMessage.length} characters`);
6949
+ log$1.log.trace(`System message: ${systemMessage.length} characters`);
4654
6950
  }
4655
6951
  const response = await client.messages.create(params);
4656
6952
  const firstContent = response.content[0];
4657
6953
  const text = firstContent && "text" in firstContent ? firstContent.text : "";
4658
- log$2.log.trace(`Assistant reply: ${text.length} characters`);
6954
+ log$1.log.trace(`Assistant reply: ${text.length} characters`);
4659
6955
  return text;
4660
6956
  }
4661
- // Structured output completion
6957
+ // Structured output completion using Anthropic's native `output_config.format`
6958
+ // field. Returns a JsonObject parsed and validated against the caller's
6959
+ // Zod schema.
4662
6960
  async function createStructuredCompletion$1(client, messages, model, responseSchema, systemMessage) {
4663
- log$2.log.trace("Using structured output");
4664
- // Get the JSON schema for the response
6961
+ log$1.log.trace("Using native structured output");
4665
6962
  const schema = responseSchema instanceof v4.z.ZodType
4666
6963
  ? responseSchema
4667
6964
  : naturalZodSchema(responseSchema);
4668
- // Set system message with JSON instructions
4669
- const defaultSystemPrompt = "You will be responding with structured JSON data. " +
4670
- "Format your entire response as a valid JSON object with the following structure: " +
4671
- JSON.stringify(v4.z.toJSONSchema(schema));
4672
- const systemPrompt = systemMessage || defaultSystemPrompt;
4673
- try {
4674
- // Use standard Anthropic API to get response
4675
- const params = {
4676
- model,
4677
- messages,
4678
- max_tokens: PROVIDER.ANTHROPIC.MAX_TOKENS.DEFAULT,
4679
- system: systemPrompt,
4680
- };
4681
- const response = await client.messages.create(params);
4682
- // Extract text from response
4683
- const firstContent = response.content[0];
4684
- const responseText = firstContent && "text" in firstContent ? firstContent.text : "";
4685
- // Find JSON in response
4686
- const jsonMatch = responseText.match(/```json\s*([\s\S]*?)\s*```/) ||
4687
- responseText.match(/\{[\s\S]*\}/);
4688
- if (jsonMatch) {
4689
- try {
4690
- // Parse the JSON response
4691
- const jsonStr = jsonMatch[1] || jsonMatch[0];
4692
- const result = JSON.parse(jsonStr);
4693
- if (!schema.parse(result)) {
4694
- throw new Error(`JSON response from Anthropic does not match schema: ${responseText}`);
4695
- }
4696
- log$2.log.trace("Received structured response", { result });
4697
- return result;
4698
- }
4699
- catch {
4700
- throw new Error(`Failed to parse JSON response from Anthropic: ${responseText}`);
4701
- }
4702
- }
4703
- // If we can't extract JSON
6965
+ const jsonSchema = v4.z.toJSONSchema(schema);
6966
+ const params = {
6967
+ model,
6968
+ messages,
6969
+ max_tokens: PROVIDER.ANTHROPIC.MAX_TOKENS.DEFAULT,
6970
+ output_config: {
6971
+ format: { type: "json_schema", schema: jsonSchema },
6972
+ },
6973
+ };
6974
+ if (systemMessage) {
6975
+ params.system = systemMessage;
6976
+ }
6977
+ const response = (await client.messages.create(params));
6978
+ if (response.stop_reason === "refusal") {
6979
+ throw new Error("Anthropic refused the structured-output request (stop_reason=refusal)");
6980
+ }
6981
+ if (response.stop_reason === "max_tokens") {
6982
+ throw new Error("Anthropic structured-output response was truncated (stop_reason=max_tokens); increase max_tokens");
6983
+ }
6984
+ const textBlock = response.content.find((block) => block.type === "text");
6985
+ if (!textBlock) {
4704
6986
  throw new Error("Failed to parse structured response from Anthropic");
4705
6987
  }
4706
- catch (error) {
4707
- log$2.log.error("Error creating structured completion", { error });
4708
- throw error;
6988
+ let parsed;
6989
+ try {
6990
+ parsed = JSON.parse(textBlock.text);
6991
+ }
6992
+ catch {
6993
+ throw new Error("Failed to parse structured response from Anthropic: " + textBlock.text);
6994
+ }
6995
+ const validation = schema.safeParse(parsed);
6996
+ if (!validation.success) {
6997
+ throw new Error(`JSON response from Anthropic does not match schema: ${textBlock.text}`);
4709
6998
  }
6999
+ log$1.log.trace("Received structured response", { result: validation.data });
7000
+ return validation.data;
4710
7001
  }
4711
7002
 
4712
7003
  // Maps Jaypie roles to Anthropic roles
@@ -4720,7 +7011,7 @@ async function createStructuredCompletion$1(client, messages, model, responseSch
4720
7011
  // Main class implementation
4721
7012
  class AnthropicProvider {
4722
7013
  constructor(model = PROVIDER.ANTHROPIC.MODEL.DEFAULT, { apiKey } = {}) {
4723
- this.log = getLogger$3();
7014
+ this.log = getLogger$5();
4724
7015
  this.conversationHistory = [];
4725
7016
  this.model = model;
4726
7017
  this.apiKey = apiKey;
@@ -4729,7 +7020,7 @@ class AnthropicProvider {
4729
7020
  if (this._client) {
4730
7021
  return this._client;
4731
7022
  }
4732
- this._client = await initializeClient$3({ apiKey: this.apiKey });
7023
+ this._client = await initializeClient$5({ apiKey: this.apiKey });
4733
7024
  return this._client;
4734
7025
  }
4735
7026
  async getOperateLoop() {
@@ -4804,6 +7095,87 @@ class AnthropicProvider {
4804
7095
  }
4805
7096
  }
4806
7097
 
7098
+ let cachedSdk$2 = null;
7099
+ async function loadSdk$2() {
7100
+ if (cachedSdk$2)
7101
+ return cachedSdk$2;
7102
+ try {
7103
+ cachedSdk$2 = await import('@aws-sdk/client-bedrock-runtime');
7104
+ return cachedSdk$2;
7105
+ }
7106
+ catch {
7107
+ throw new errors.ConfigurationError("@aws-sdk/client-bedrock-runtime is required but not installed. Run: npm install @aws-sdk/client-bedrock-runtime");
7108
+ }
7109
+ }
7110
+ const getLogger$4 = () => log$1.log.lib({ lib: kit.JAYPIE.LIB.LLM });
7111
+ async function initializeClient$4({ region, } = {}) {
7112
+ const logger = getLogger$4();
7113
+ const resolvedRegion = region || process.env.AWS_REGION || "us-east-1";
7114
+ const sdk = await loadSdk$2();
7115
+ const client = new sdk.BedrockRuntimeClient({ region: resolvedRegion });
7116
+ logger.trace("Initialized Bedrock client");
7117
+ return client;
7118
+ }
7119
+
7120
+ class BedrockProvider {
7121
+ constructor(model = PROVIDER.BEDROCK.MODEL.DEFAULT, { region } = {}) {
7122
+ this.log = getLogger$4();
7123
+ this.conversationHistory = [];
7124
+ this.model = model;
7125
+ this.region = region;
7126
+ }
7127
+ async getClient() {
7128
+ if (this._client)
7129
+ return this._client;
7130
+ this._client = await initializeClient$4({ region: this.region });
7131
+ return this._client;
7132
+ }
7133
+ async getOperateLoop() {
7134
+ if (this._operateLoop)
7135
+ return this._operateLoop;
7136
+ const client = await this.getClient();
7137
+ this._operateLoop = createOperateLoop({ adapter: bedrockAdapter, client });
7138
+ return this._operateLoop;
7139
+ }
7140
+ async getStreamLoop() {
7141
+ if (this._streamLoop)
7142
+ return this._streamLoop;
7143
+ const client = await this.getClient();
7144
+ this._streamLoop = createStreamLoop({ adapter: bedrockAdapter, client });
7145
+ return this._streamLoop;
7146
+ }
7147
+ async send(message, options) {
7148
+ const operateLoop = await this.getOperateLoop();
7149
+ const mergedOptions = { ...options, model: options?.model ?? this.model };
7150
+ const response = await operateLoop.execute(message, mergedOptions);
7151
+ return response.content ?? "";
7152
+ }
7153
+ async operate(input, options = {}) {
7154
+ const operateLoop = await this.getOperateLoop();
7155
+ const mergedOptions = { ...options, model: options.model ?? this.model };
7156
+ if (this.conversationHistory.length > 0) {
7157
+ mergedOptions.history = options.history
7158
+ ? [...this.conversationHistory, ...options.history]
7159
+ : [...this.conversationHistory];
7160
+ }
7161
+ const response = await operateLoop.execute(input, mergedOptions);
7162
+ if (response.history && response.history.length > 0) {
7163
+ this.conversationHistory = response.history;
7164
+ }
7165
+ return response;
7166
+ }
7167
+ async *stream(input, options = {}) {
7168
+ const streamLoop = await this.getStreamLoop();
7169
+ const mergedOptions = { ...options, model: options.model ?? this.model };
7170
+ if (this.conversationHistory.length > 0) {
7171
+ mergedOptions.history = options.history
7172
+ ? [...this.conversationHistory, ...options.history]
7173
+ : [...this.conversationHistory];
7174
+ }
7175
+ yield* streamLoop.execute(input, mergedOptions);
7176
+ }
7177
+ }
7178
+
4807
7179
  // SDK loader with caching
4808
7180
  let cachedSdk$1 = null;
4809
7181
  async function loadSdk$1() {
@@ -4818,10 +7190,10 @@ async function loadSdk$1() {
4818
7190
  }
4819
7191
  }
4820
7192
  // Logger
4821
- const getLogger$2 = () => log$2.log.lib({ lib: kit.JAYPIE.LIB.LLM });
7193
+ const getLogger$3 = () => log$1.log.lib({ lib: kit.JAYPIE.LIB.LLM });
4822
7194
  // Client initialization
4823
- async function initializeClient$2({ apiKey, } = {}) {
4824
- const logger = getLogger$2();
7195
+ async function initializeClient$3({ apiKey, } = {}) {
7196
+ const logger = getLogger$3();
4825
7197
  const resolvedApiKey = apiKey || (await aws.getEnvSecret("GEMINI_API_KEY"));
4826
7198
  if (!resolvedApiKey) {
4827
7199
  throw new errors.ConfigurationError("The application could not resolve the requested keys");
@@ -4841,21 +7213,20 @@ function formatUserMessage$2(message, { data, placeholders } = {}) {
4841
7213
  };
4842
7214
  }
4843
7215
  function prepareMessages$2(message, { data, placeholders } = {}) {
4844
- const logger = getLogger$2();
7216
+ const logger = getLogger$3();
4845
7217
  const messages = [];
4846
- let systemInstruction;
4847
7218
  // Note: Gemini handles system prompts differently via systemInstruction config
4848
7219
  // This function is kept for compatibility but system prompts should be passed
4849
7220
  // via the systemInstruction parameter in generateContent
4850
7221
  const userMessage = formatUserMessage$2(message, { data, placeholders });
4851
7222
  messages.push(userMessage);
4852
7223
  logger.trace(`User message: ${userMessage.content?.length} characters`);
4853
- return { messages, systemInstruction };
7224
+ return { messages, systemInstruction: undefined };
4854
7225
  }
4855
7226
 
4856
- class GeminiProvider {
4857
- constructor(model = PROVIDER.GEMINI.MODEL.DEFAULT, { apiKey } = {}) {
4858
- this.log = getLogger$2();
7227
+ class GoogleProvider {
7228
+ constructor(model = PROVIDER.GOOGLE.MODEL.DEFAULT, { apiKey } = {}) {
7229
+ this.log = getLogger$3();
4859
7230
  this.conversationHistory = [];
4860
7231
  this.model = model;
4861
7232
  this.apiKey = apiKey;
@@ -4864,7 +7235,7 @@ class GeminiProvider {
4864
7235
  if (this._client) {
4865
7236
  return this._client;
4866
7237
  }
4867
- this._client = await initializeClient$2({ apiKey: this.apiKey });
7238
+ this._client = await initializeClient$3({ apiKey: this.apiKey });
4868
7239
  return this._client;
4869
7240
  }
4870
7241
  async getOperateLoop() {
@@ -4873,7 +7244,7 @@ class GeminiProvider {
4873
7244
  }
4874
7245
  const client = await this.getClient();
4875
7246
  this._operateLoop = createOperateLoop({
4876
- adapter: geminiAdapter,
7247
+ adapter: googleAdapter,
4877
7248
  client,
4878
7249
  });
4879
7250
  return this._operateLoop;
@@ -4884,7 +7255,7 @@ class GeminiProvider {
4884
7255
  }
4885
7256
  const client = await this.getClient();
4886
7257
  this._streamLoop = createStreamLoop({
4887
- adapter: geminiAdapter,
7258
+ adapter: googleAdapter,
4888
7259
  client,
4889
7260
  });
4890
7261
  return this._streamLoop;
@@ -4920,6 +7291,17 @@ class GeminiProvider {
4920
7291
  return JSON.parse(text);
4921
7292
  }
4922
7293
  catch {
7294
+ // Strip markdown code fences and retry (Gemini sometimes wraps JSON in fences)
7295
+ const jsonMatch = text.match(/```(?:json)?\s*([\s\S]*?)\s*```/) ||
7296
+ text.match(/\{[\s\S]*\}/);
7297
+ if (jsonMatch) {
7298
+ try {
7299
+ return JSON.parse(jsonMatch[1] || jsonMatch[0]);
7300
+ }
7301
+ catch {
7302
+ // Fall through to return original text
7303
+ }
7304
+ }
4923
7305
  return text || "";
4924
7306
  }
4925
7307
  }
@@ -4958,10 +7340,10 @@ class GeminiProvider {
4958
7340
  }
4959
7341
 
4960
7342
  // Logger
4961
- const getLogger$1 = () => log$2.log.lib({ lib: kit.JAYPIE.LIB.LLM });
7343
+ const getLogger$2 = () => log$1.log.lib({ lib: kit.JAYPIE.LIB.LLM });
4962
7344
  // Client initialization
4963
- async function initializeClient$1({ apiKey, } = {}) {
4964
- const logger = getLogger$1();
7345
+ async function initializeClient$2({ apiKey, } = {}) {
7346
+ const logger = getLogger$2();
4965
7347
  const resolvedApiKey = apiKey || (await aws.getEnvSecret("OPENAI_API_KEY"));
4966
7348
  if (!resolvedApiKey) {
4967
7349
  throw new errors.ConfigurationError("The application could not resolve the requested keys");
@@ -4989,7 +7371,7 @@ function formatUserMessage$1(message, { data, placeholders } = {}) {
4989
7371
  };
4990
7372
  }
4991
7373
  function prepareMessages$1(message, { system, data, placeholders } = {}) {
4992
- const logger = getLogger$1();
7374
+ const logger = getLogger$2();
4993
7375
  const messages = [];
4994
7376
  if (system) {
4995
7377
  const systemMessage = formatSystemMessage$1(system, { data, placeholders });
@@ -5003,7 +7385,7 @@ function prepareMessages$1(message, { system, data, placeholders } = {}) {
5003
7385
  }
5004
7386
  // Completion requests
5005
7387
  async function createStructuredCompletion(client, { messages, responseSchema, model, }) {
5006
- const logger = getLogger$1();
7388
+ const logger = getLogger$2();
5007
7389
  logger.trace("Using structured output");
5008
7390
  const zodSchema = responseSchema instanceof v4.z.ZodType
5009
7391
  ? responseSchema
@@ -5034,7 +7416,7 @@ async function createStructuredCompletion(client, { messages, responseSchema, mo
5034
7416
  return completion.choices[0].message.parsed;
5035
7417
  }
5036
7418
  async function createTextCompletion(client, { messages, model, }) {
5037
- const logger = getLogger$1();
7419
+ const logger = getLogger$2();
5038
7420
  logger.trace("Using text output (unstructured)");
5039
7421
  const completion = await client.chat.completions.create({
5040
7422
  messages,
@@ -5046,7 +7428,7 @@ async function createTextCompletion(client, { messages, model, }) {
5046
7428
 
5047
7429
  class OpenAiProvider {
5048
7430
  constructor(model = PROVIDER.OPENAI.MODEL.DEFAULT, { apiKey } = {}) {
5049
- this.log = getLogger$1();
7431
+ this.log = getLogger$2();
5050
7432
  this.conversationHistory = [];
5051
7433
  this.model = model;
5052
7434
  this.apiKey = apiKey;
@@ -5055,7 +7437,7 @@ class OpenAiProvider {
5055
7437
  if (this._client) {
5056
7438
  return this._client;
5057
7439
  }
5058
- this._client = await initializeClient$1({ apiKey: this.apiKey });
7440
+ this._client = await initializeClient$2({ apiKey: this.apiKey });
5059
7441
  return this._client;
5060
7442
  }
5061
7443
  async getOperateLoop() {
@@ -5142,10 +7524,10 @@ async function loadSdk() {
5142
7524
  }
5143
7525
  }
5144
7526
  // Logger
5145
- const getLogger = () => log$2.log.lib({ lib: kit.JAYPIE.LIB.LLM });
7527
+ const getLogger$1 = () => log$1.log.lib({ lib: kit.JAYPIE.LIB.LLM });
5146
7528
  // Client initialization
5147
- async function initializeClient({ apiKey, } = {}) {
5148
- const logger = getLogger();
7529
+ async function initializeClient$1({ apiKey, } = {}) {
7530
+ const logger = getLogger$1();
5149
7531
  const resolvedApiKey = apiKey || (await aws.getEnvSecret("OPENROUTER_API_KEY"));
5150
7532
  if (!resolvedApiKey) {
5151
7533
  throw new errors.ConfigurationError("The application could not resolve the requested keys");
@@ -5178,7 +7560,7 @@ function formatUserMessage(message, { data, placeholders } = {}) {
5178
7560
  };
5179
7561
  }
5180
7562
  function prepareMessages(message, { system, data, placeholders } = {}) {
5181
- const logger = getLogger();
7563
+ const logger = getLogger$1();
5182
7564
  const messages = [];
5183
7565
  if (system) {
5184
7566
  const systemMessage = formatSystemMessage(system, { data, placeholders });
@@ -5193,7 +7575,7 @@ function prepareMessages(message, { system, data, placeholders } = {}) {
5193
7575
 
5194
7576
  class OpenRouterProvider {
5195
7577
  constructor(model = getDefaultModel(), { apiKey } = {}) {
5196
- this.log = getLogger();
7578
+ this.log = getLogger$1();
5197
7579
  this.conversationHistory = [];
5198
7580
  this.model = model;
5199
7581
  this.apiKey = apiKey;
@@ -5202,7 +7584,7 @@ class OpenRouterProvider {
5202
7584
  if (this._client) {
5203
7585
  return this._client;
5204
7586
  }
5205
- this._client = await initializeClient({ apiKey: this.apiKey });
7587
+ this._client = await initializeClient$1({ apiKey: this.apiKey });
5206
7588
  return this._client;
5207
7589
  }
5208
7590
  async getOperateLoop() {
@@ -5290,11 +7672,116 @@ class OpenRouterProvider {
5290
7672
  }
5291
7673
  }
5292
7674
 
7675
+ // Logger
7676
+ const getLogger = () => log$1.log.lib({ lib: kit.JAYPIE.LIB.LLM });
7677
+ // Client initialization
7678
+ async function initializeClient({ apiKey, } = {}) {
7679
+ const logger = getLogger();
7680
+ const resolvedApiKey = apiKey || (await aws.getEnvSecret(PROVIDER.XAI.API_KEY));
7681
+ if (!resolvedApiKey) {
7682
+ throw new errors.ConfigurationError("The application could not resolve the requested keys");
7683
+ }
7684
+ const client = new openai.OpenAI({
7685
+ apiKey: resolvedApiKey,
7686
+ baseURL: PROVIDER.XAI.BASE_URL,
7687
+ });
7688
+ logger.trace("Initialized xAI client");
7689
+ return client;
7690
+ }
7691
+
7692
+ class XaiProvider {
7693
+ constructor(model = PROVIDER.XAI.MODEL.DEFAULT, { apiKey } = {}) {
7694
+ this.log = getLogger$2();
7695
+ this.conversationHistory = [];
7696
+ this.model = model;
7697
+ this.apiKey = apiKey;
7698
+ }
7699
+ async getClient() {
7700
+ if (this._client) {
7701
+ return this._client;
7702
+ }
7703
+ this._client = await initializeClient({ apiKey: this.apiKey });
7704
+ return this._client;
7705
+ }
7706
+ async getOperateLoop() {
7707
+ if (this._operateLoop) {
7708
+ return this._operateLoop;
7709
+ }
7710
+ const client = await this.getClient();
7711
+ this._operateLoop = createOperateLoop({
7712
+ adapter: xaiAdapter,
7713
+ client,
7714
+ });
7715
+ return this._operateLoop;
7716
+ }
7717
+ async getStreamLoop() {
7718
+ if (this._streamLoop) {
7719
+ return this._streamLoop;
7720
+ }
7721
+ const client = await this.getClient();
7722
+ this._streamLoop = createStreamLoop({
7723
+ adapter: xaiAdapter,
7724
+ client,
7725
+ });
7726
+ return this._streamLoop;
7727
+ }
7728
+ async send(message, options) {
7729
+ const client = await this.getClient();
7730
+ const messages = prepareMessages$1(message, options || {});
7731
+ const modelToUse = options?.model || this.model;
7732
+ if (options?.response) {
7733
+ return createStructuredCompletion(client, {
7734
+ messages,
7735
+ responseSchema: options.response,
7736
+ model: modelToUse,
7737
+ });
7738
+ }
7739
+ return createTextCompletion(client, {
7740
+ messages,
7741
+ model: modelToUse,
7742
+ });
7743
+ }
7744
+ async operate(input, options = {}) {
7745
+ const operateLoop = await this.getOperateLoop();
7746
+ const mergedOptions = { ...options, model: options.model ?? this.model };
7747
+ // Create a merged history including both the tracked history and any explicitly provided history
7748
+ if (this.conversationHistory.length > 0) {
7749
+ // If options.history exists, merge with instance history, otherwise use instance history
7750
+ mergedOptions.history = options.history
7751
+ ? [...this.conversationHistory, ...options.history]
7752
+ : [...this.conversationHistory];
7753
+ }
7754
+ // Execute operate loop
7755
+ const response = await operateLoop.execute(input, mergedOptions);
7756
+ // Update conversation history with the new history from the response
7757
+ if (response.history && response.history.length > 0) {
7758
+ this.conversationHistory = response.history;
7759
+ }
7760
+ return response;
7761
+ }
7762
+ async *stream(input, options = {}) {
7763
+ const streamLoop = await this.getStreamLoop();
7764
+ const mergedOptions = { ...options, model: options.model ?? this.model };
7765
+ // Create a merged history including both the tracked history and any explicitly provided history
7766
+ if (this.conversationHistory.length > 0) {
7767
+ mergedOptions.history = options.history
7768
+ ? [...this.conversationHistory, ...options.history]
7769
+ : [...this.conversationHistory];
7770
+ }
7771
+ // Execute stream loop
7772
+ yield* streamLoop.execute(input, mergedOptions);
7773
+ }
7774
+ }
7775
+
5293
7776
  class Llm {
5294
7777
  constructor(providerName = DEFAULT.PROVIDER.NAME, options = {}) {
5295
7778
  const { fallback, model } = options;
5296
7779
  let finalProvider = providerName;
5297
7780
  let finalModel = model;
7781
+ // Legacy: accept "gemini" but warn
7782
+ if (providerName === "gemini") {
7783
+ log$1.warn(`Provider "gemini" is deprecated, use "${PROVIDER.GOOGLE.NAME}" instead`);
7784
+ }
5298
7785
  if (model) {
5299
7786
  const modelDetermined = determineModelProvider(model);
5300
7787
  finalModel = modelDetermined.model;
@@ -5309,6 +7796,10 @@ class Llm {
5309
7796
  throw new errors.ConfigurationError(`Unable to determine provider from: ${providerName}`);
5310
7797
  }
5311
7798
  finalProvider = providerDetermined.provider;
7799
+ // When providerName is actually a model name, extract the model (#213)
7800
+ if (!finalModel && providerName !== providerDetermined.provider) {
7801
+ finalModel = providerDetermined.model;
7802
+ }
5312
7803
  }
5313
7804
  // Handle conflicts: if both providerName and model specify different providers
5314
7805
  if (model && providerName !== DEFAULT.PROVIDER.NAME) {
@@ -5331,8 +7822,10 @@ class Llm {
5331
7822
  switch (providerName) {
5332
7823
  case PROVIDER.ANTHROPIC.NAME:
5333
7824
  return new AnthropicProvider(model || PROVIDER.ANTHROPIC.MODEL.DEFAULT, { apiKey });
5334
- case PROVIDER.GEMINI.NAME:
5335
- return new GeminiProvider(model || PROVIDER.GEMINI.MODEL.DEFAULT, {
7825
+ case PROVIDER.BEDROCK.NAME:
7826
+ return new BedrockProvider(model || PROVIDER.BEDROCK.MODEL.DEFAULT);
7827
+ case PROVIDER.GOOGLE.NAME:
7828
+ return new GoogleProvider(model || PROVIDER.GOOGLE.MODEL.DEFAULT, {
5336
7829
  apiKey,
5337
7830
  });
5338
7831
  case PROVIDER.OPENAI.NAME:
@@ -5343,6 +7836,10 @@ class Llm {
5343
7836
  return new OpenRouterProvider(model || PROVIDER.OPENROUTER.MODEL.DEFAULT, {
5344
7837
  apiKey,
5345
7838
  });
7839
+ case PROVIDER.XAI.NAME:
7840
+ return new XaiProvider(model || PROVIDER.XAI.MODEL.DEFAULT, {
7841
+ apiKey,
7842
+ });
5346
7843
  default:
5347
7844
  throw new errors.ConfigurationError(`Unsupported provider: ${providerName}`);
5348
7845
  }
@@ -5397,7 +7894,7 @@ class Llm {
5397
7894
  }
5398
7895
  catch (error) {
5399
7896
  lastError = error;
5400
- log$2.warn(`Provider ${this._provider} failed`, {
7897
+ log$1.warn(`Provider ${this._provider} failed`, {
5401
7898
  error: lastError.message,
5402
7899
  fallbacksRemaining: fallbackChain.length,
5403
7900
  });
@@ -5417,7 +7914,7 @@ class Llm {
5417
7914
  }
5418
7915
  catch (error) {
5419
7916
  lastError = error;
5420
- log$2.warn(`Fallback provider ${fallbackConfig.provider} failed`, {
7917
+ log$1.warn(`Fallback provider ${fallbackConfig.provider} failed`, {
5421
7918
  error: lastError.message,
5422
7919
  fallbacksRemaining: fallbackChain.length - attempts + 1,
5423
7920
  });
@@ -5586,16 +8083,17 @@ const roll = {
5586
8083
  },
5587
8084
  type: "function",
5588
8085
  call: ({ number = 1, sides = 6 } = {}) => {
8086
+ const log = getLogger$6();
5589
8087
  const rng = random$1();
5590
8088
  const rolls = [];
5591
8089
  let total = 0;
5592
8090
  const parsedNumber = tryParseNumber(number, {
5593
8091
  defaultValue: 1,
5594
- warnFunction: log$1.warn,
8092
+ warnFunction: log.warn,
5595
8093
  });
5596
8094
  const parsedSides = tryParseNumber(sides, {
5597
8095
  defaultValue: 6,
5598
- warnFunction: log$1.warn,
8096
+ warnFunction: log.warn,
5599
8097
  });
5600
8098
  for (let i = 0; i < parsedNumber; i++) {
5601
8099
  const rollValue = rng({ min: 1, max: parsedSides, integer: true });
@@ -5748,8 +8246,10 @@ const weather = {
5748
8246
  }
5749
8247
  catch (error) {
5750
8248
  if (error instanceof Error) {
8249
+ // eslint-disable-next-line preserve-caught-error -- package targets ES2020; Error `cause` option requires ES2022
5751
8250
  throw new Error(`Weather API error: ${error.message}`);
5752
8251
  }
8252
+ // eslint-disable-next-line preserve-caught-error -- package targets ES2020; Error `cause` option requires ES2022
5753
8253
  throw new Error("Unknown error occurred while fetching weather data");
5754
8254
  }
5755
8255
  },
@@ -5781,12 +8281,15 @@ const toolkit = new JaypieToolkit(tools);
5781
8281
  [exports.LlmMessageRole.Developer]: "user",
5782
8282
  });
5783
8283
 
5784
- exports.GeminiProvider = GeminiProvider;
8284
+ exports.BedrockProvider = BedrockProvider;
8285
+ exports.GeminiProvider = GoogleProvider;
8286
+ exports.GoogleProvider = GoogleProvider;
5785
8287
  exports.JaypieToolkit = JaypieToolkit;
5786
8288
  exports.LLM = constants;
5787
8289
  exports.Llm = Llm;
5788
8290
  exports.OpenRouterProvider = OpenRouterProvider;
5789
8291
  exports.Toolkit = Toolkit;
8292
+ exports.XaiProvider = XaiProvider;
5790
8293
  exports.extractReasoning = extractReasoning;
5791
8294
  exports.isLlmOperateInput = isLlmOperateInput;
5792
8295
  exports.isLlmOperateInputContent = isLlmOperateInputContent;