@jaypie/llm 1.3.11 → 1.3.13

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.
@@ -37,6 +37,14 @@ const MODEL = {
37
37
  SONNET: "claude-sonnet-5",
38
38
  HAIKU: "claude-haiku-4-5",
39
39
  MYTHOS: "claude-mythos-5",
40
+ // Fireworks (serverless open models; ids provided by operator)
41
+ FIREWORKS: {
42
+ DEEPSEEK: "accounts/fireworks/models/deepseek-v4-pro",
43
+ GLM: "accounts/fireworks/models/glm-5p2",
44
+ KIMI: "accounts/fireworks/models/kimi-k2p7-code",
45
+ MINIMAX: "accounts/fireworks/models/minimax-m2p7",
46
+ QWEN: "accounts/fireworks/models/qwen3p7-plus",
47
+ },
40
48
  // Google
41
49
  GEMINI_FLASH: "gemini-3.5-flash",
42
50
  GEMINI_FLASH_LITE: "gemini-3.1-flash-lite",
@@ -141,6 +149,14 @@ const PROVIDER = {
141
149
  SCHEMA_VERSION: "v2",
142
150
  },
143
151
  },
152
+ FIREWORKS: {
153
+ // https://docs.fireworks.ai/
154
+ API_KEY: "FIREWORKS_API_KEY",
155
+ BASE_URL: "https://api.fireworks.ai/inference/v1",
156
+ DEFAULT: MODEL.FIREWORKS.GLM,
157
+ MODEL_MATCH_WORDS: ["fireworks"],
158
+ NAME: "fireworks",
159
+ },
144
160
  /** @deprecated Use PROVIDER.GOOGLE — "Google" is the provider; Gemini is the model family */
145
161
  GEMINI: GOOGLE_PROVIDER,
146
162
  GOOGLE: GOOGLE_PROVIDER,
@@ -278,6 +294,14 @@ function determineModelProvider(input) {
278
294
  provider: PROVIDER.OPENROUTER.NAME,
279
295
  };
280
296
  }
297
+ // Check for explicit fireworks: prefix
298
+ if (input.startsWith("fireworks:")) {
299
+ const model = input.slice("fireworks:".length);
300
+ return {
301
+ model,
302
+ provider: PROVIDER.FIREWORKS.NAME,
303
+ };
304
+ }
281
305
  // Check for explicit bedrock: prefix
282
306
  if (input.startsWith("bedrock:")) {
283
307
  const model = input.slice("bedrock:".length);
@@ -293,6 +317,12 @@ function determineModelProvider(input) {
293
317
  provider: PROVIDER.BEDROCK.NAME,
294
318
  };
295
319
  }
320
+ if (input === PROVIDER.FIREWORKS.NAME) {
321
+ return {
322
+ model: PROVIDER.FIREWORKS.DEFAULT,
323
+ provider: PROVIDER.FIREWORKS.NAME,
324
+ };
325
+ }
296
326
  if (input === PROVIDER.ANTHROPIC.NAME) {
297
327
  return {
298
328
  model: PROVIDER.ANTHROPIC.DEFAULT,
@@ -325,6 +355,18 @@ function determineModelProvider(input) {
325
355
  }
326
356
  // Exact model ids are classified by the "/" rule and MODEL_MATCH_WORDS below,
327
357
  // so no per-provider id catalog is consulted here.
358
+ // Check Fireworks match words before the "/" rule — Fireworks ids contain
359
+ // "/" (e.g., "accounts/fireworks/models/glm-5p2") and would otherwise route
360
+ // to OpenRouter
361
+ const lowerInputEarly = input.toLowerCase();
362
+ for (const matchWord of PROVIDER.FIREWORKS.MODEL_MATCH_WORDS) {
363
+ if (lowerInputEarly.includes(matchWord)) {
364
+ return {
365
+ model: input,
366
+ provider: PROVIDER.FIREWORKS.NAME,
367
+ };
368
+ }
369
+ }
328
370
  // Assume OpenRouter for models containing "/" (e.g., "openai/gpt-4", "anthropic/claude-3-opus")
329
371
  // This check must come before match words so that "openai/gpt-4" is not matched by "openai" keyword
330
372
  if (input.includes("/")) {
@@ -443,6 +485,15 @@ function resolveModelChain(model) {
443
485
  * Providers can extend this class to reduce boilerplate.
444
486
  */
445
487
  class BaseProviderAdapter {
488
+ constructor() {
489
+ /**
490
+ * Whether OperateLoop may take a corrective turn when a `format` request
491
+ * completes with prose instead of structured output. Providers whose
492
+ * structured output rides a tool emulation (where compliance is a model
493
+ * decision) opt in; native grammar-constrained providers do not need it.
494
+ */
495
+ this.supportsStructuredOutputRetry = false;
496
+ }
446
497
  /**
447
498
  * Default implementation checks if error is retryable via classifyError
448
499
  */
@@ -576,6 +627,19 @@ const GEMINI_THINKING_BUDGET = {
576
627
  function toGeminiThinkingBudget(effort) {
577
628
  return { papered: false, value: GEMINI_THINKING_BUDGET[effort] };
578
629
  }
630
+ // Fireworks `reasoning_effort` — low | medium | high. Accepted on every model
631
+ // (the API no-ops where unsupported). No sub-low or top rung, so `lowest`
632
+ // collapses onto `low` and `highest` onto `high`.
633
+ const FIREWORKS_EFFORT = {
634
+ [EFFORT.LOWEST]: { papered: true, value: "low" },
635
+ [EFFORT.LOW]: { papered: false, value: "low" },
636
+ [EFFORT.MEDIUM]: { papered: false, value: "medium" },
637
+ [EFFORT.HIGH]: { papered: false, value: "high" },
638
+ [EFFORT.HIGHEST]: { papered: true, value: "high" },
639
+ };
640
+ function toFireworksEffort(effort) {
641
+ return FIREWORKS_EFFORT[effort];
642
+ }
579
643
  // OpenRouter `reasoning.effort` — accepts the full minimal..xhigh ladder and
580
644
  // maps to the routed provider's nearest supported level itself, so nothing is
581
645
  // papered here.
@@ -1062,7 +1126,7 @@ function jsonSchemaToOpenApi3(schema) {
1062
1126
  return result;
1063
1127
  }
1064
1128
 
1065
- const getLogger$6 = () => log$1.log.lib({ lib: kit.JAYPIE.LIB.LLM });
1129
+ const getLogger$7 = () => log$1.log.lib({ lib: kit.JAYPIE.LIB.LLM });
1066
1130
 
1067
1131
  //
1068
1132
  //
@@ -1718,7 +1782,7 @@ function classifyProviderError(error) {
1718
1782
  //
1719
1783
  // Constants
1720
1784
  //
1721
- const STRUCTURED_OUTPUT_TOOL_NAME$3 = "structured_output";
1785
+ const STRUCTURED_OUTPUT_TOOL_NAME$4 = "structured_output";
1722
1786
  /**
1723
1787
  * Whether `output_config.effort` may be sent for this model. Anthropic added
1724
1788
  * the effort control on the Claude 4.5 line and up (the 5 family); older models
@@ -1954,7 +2018,7 @@ const MODELS_WITHOUT_TEMPERATURE$2 = [
1954
2018
  /^claude-opus-4-[789]/,
1955
2019
  /^claude-opus-[5-9]/,
1956
2020
  ];
1957
- function isTemperatureDeprecationError$3(error) {
2021
+ function isTemperatureDeprecationError$4(error) {
1958
2022
  if (!error || typeof error !== "object")
1959
2023
  return false;
1960
2024
  const err = error;
@@ -1973,7 +2037,7 @@ function isTemperatureDeprecationError$3(error) {
1973
2037
  * field) is also explicitly excluded — that's a code-path bug, not a model
1974
2038
  * gap; it should propagate so we notice and fix it.
1975
2039
  */
1976
- function isStructuredOutputUnsupportedError$1(error) {
2040
+ function isStructuredOutputUnsupportedError$2(error) {
1977
2041
  if (!error || typeof error !== "object")
1978
2042
  return false;
1979
2043
  const err = error;
@@ -2106,7 +2170,7 @@ class AnthropicAdapter extends BaseProviderAdapter {
2106
2170
  if (useFallbackStructuredOutput && request.format) {
2107
2171
  log$1.log.warn(`[AnthropicAdapter] Using legacy structured_output tool fallback for model ${anthropicRequest.model}; native output_config previously rejected for this model.`);
2108
2172
  allTools.push({
2109
- name: STRUCTURED_OUTPUT_TOOL_NAME$3,
2173
+ name: STRUCTURED_OUTPUT_TOOL_NAME$4,
2110
2174
  description: "Output a structured JSON object, " +
2111
2175
  "use this before your final response to give structured outputs to the user",
2112
2176
  parameters: request.format,
@@ -2223,7 +2287,7 @@ class AnthropicAdapter extends BaseProviderAdapter {
2223
2287
  return undefined;
2224
2288
  // If the model rejected `temperature`, cache it and retry without the param
2225
2289
  if (anthropicRequest.temperature !== undefined &&
2226
- isTemperatureDeprecationError$3(error)) {
2290
+ isTemperatureDeprecationError$4(error)) {
2227
2291
  this.rememberModelRejectsTemperature(anthropicRequest.model);
2228
2292
  const retryRequest = { ...anthropicRequest };
2229
2293
  delete retryRequest.temperature;
@@ -2235,7 +2299,7 @@ class AnthropicAdapter extends BaseProviderAdapter {
2235
2299
  }
2236
2300
  // If the model rejected native structured output, cache it and retry
2237
2301
  // via the legacy fake-tool emulation path.
2238
- if (wantsStructuredOutput && isStructuredOutputUnsupportedError$1(error)) {
2302
+ if (wantsStructuredOutput && isStructuredOutputUnsupportedError$2(error)) {
2239
2303
  const model = anthropicRequest.model;
2240
2304
  this.rememberModelRejectsStructuredOutput(model);
2241
2305
  log$1.log.warn(`[AnthropicAdapter] Model ${model} rejected native output_config; falling back to legacy structured_output tool emulation.`);
@@ -2260,7 +2324,7 @@ class AnthropicAdapter extends BaseProviderAdapter {
2260
2324
  fallbackRequest.output_config = { effort: output_config.effort };
2261
2325
  }
2262
2326
  const fakeTool = {
2263
- name: STRUCTURED_OUTPUT_TOOL_NAME$3,
2327
+ name: STRUCTURED_OUTPUT_TOOL_NAME$4,
2264
2328
  description: "Output a structured JSON object, " +
2265
2329
  "use this before your final response to give structured outputs to the user",
2266
2330
  input_schema: {
@@ -2288,7 +2352,7 @@ class AnthropicAdapter extends BaseProviderAdapter {
2288
2352
  }
2289
2353
  catch (error) {
2290
2354
  if (streamRequest.temperature !== undefined &&
2291
- isTemperatureDeprecationError$3(error)) {
2355
+ isTemperatureDeprecationError$4(error)) {
2292
2356
  this.rememberModelRejectsTemperature(streamRequest.model);
2293
2357
  streamRequest = {
2294
2358
  ...streamRequest,
@@ -2551,7 +2615,7 @@ class AnthropicAdapter extends BaseProviderAdapter {
2551
2615
  // runtime has cached as not supporting `output_format`.
2552
2616
  const lastBlock = anthropicResponse.content[anthropicResponse.content.length - 1];
2553
2617
  return (lastBlock?.type === "tool_use" &&
2554
- lastBlock.name === STRUCTURED_OUTPUT_TOOL_NAME$3);
2618
+ lastBlock.name === STRUCTURED_OUTPUT_TOOL_NAME$4);
2555
2619
  }
2556
2620
  extractStructuredOutput(response) {
2557
2621
  const anthropicResponse = response;
@@ -2577,7 +2641,7 @@ class AnthropicAdapter extends BaseProviderAdapter {
2577
2641
  // Fallback path: legacy fake-tool emulation
2578
2642
  const lastBlock = anthropicResponse.content[anthropicResponse.content.length - 1];
2579
2643
  if (lastBlock?.type === "tool_use" &&
2580
- lastBlock.name === STRUCTURED_OUTPUT_TOOL_NAME$3) {
2644
+ lastBlock.name === STRUCTURED_OUTPUT_TOOL_NAME$4) {
2581
2645
  return lastBlock.input;
2582
2646
  }
2583
2647
  return undefined;
@@ -2666,12 +2730,12 @@ function convertContentToBedrock(content) {
2666
2730
  //
2667
2731
  // Constants / helpers
2668
2732
  //
2669
- const STRUCTURED_OUTPUT_TOOL_NAME$2 = "structured_output";
2733
+ const STRUCTURED_OUTPUT_TOOL_NAME$3 = "structured_output";
2670
2734
  function isOutputConfigUnsupportedError(error) {
2671
2735
  const msg = error?.message ?? "";
2672
2736
  return /outputConfig|output_config/i.test(msg);
2673
2737
  }
2674
- function isTemperatureDeprecationError$2(error) {
2738
+ function isTemperatureDeprecationError$3(error) {
2675
2739
  const msg = error?.message ?? "";
2676
2740
  return /temperature.*deprecated|deprecated.*temperature/i.test(msg);
2677
2741
  }
@@ -2818,7 +2882,7 @@ class BedrockAdapter extends BaseProviderAdapter {
2818
2882
  if (this.useFakeToolForStructuredOutput(model)) {
2819
2883
  const fakeTool = {
2820
2884
  toolSpec: {
2821
- name: STRUCTURED_OUTPUT_TOOL_NAME$2,
2885
+ name: STRUCTURED_OUTPUT_TOOL_NAME$3,
2822
2886
  description: "REQUIRED: You MUST call this tool to provide your final response. " +
2823
2887
  "After gathering all necessary information (including results from other tools), " +
2824
2888
  "call this tool with the structured data to complete the request.",
@@ -2887,7 +2951,7 @@ class BedrockAdapter extends BaseProviderAdapter {
2887
2951
  }
2888
2952
  catch (error) {
2889
2953
  if (bedrockRequest.inferenceConfig?.temperature !== undefined &&
2890
- isTemperatureDeprecationError$2(error)) {
2954
+ isTemperatureDeprecationError$3(error)) {
2891
2955
  this.rememberModelRejectsTemperature(bedrockRequest.modelId || this.defaultModel);
2892
2956
  const retryRequest = {
2893
2957
  ...bedrockRequest,
@@ -2921,7 +2985,7 @@ class BedrockAdapter extends BaseProviderAdapter {
2921
2985
  }
2922
2986
  const fakeTool = {
2923
2987
  toolSpec: {
2924
- name: STRUCTURED_OUTPUT_TOOL_NAME$2,
2988
+ name: STRUCTURED_OUTPUT_TOOL_NAME$3,
2925
2989
  description: "REQUIRED: You MUST call this tool to provide your final response. " +
2926
2990
  "After gathering all necessary information (including results from other tools), " +
2927
2991
  "call this tool with the structured data to complete the request.",
@@ -3010,7 +3074,7 @@ class BedrockAdapter extends BaseProviderAdapter {
3010
3074
  // Don't surface structured_output fake tool as a real tool call
3011
3075
  const allToolUses = (bedrockResponse.output?.message?.content ?? []).filter((b) => "toolUse" in b);
3012
3076
  const hasOnlyStructuredOutputTool = allToolUses.length > 0 &&
3013
- allToolUses.every((b) => b.toolUse.name === STRUCTURED_OUTPUT_TOOL_NAME$2);
3077
+ allToolUses.every((b) => b.toolUse.name === STRUCTURED_OUTPUT_TOOL_NAME$3);
3014
3078
  const hasToolCalls = bedrockResponse.stopReason === "tool_use" && !hasOnlyStructuredOutputTool;
3015
3079
  return {
3016
3080
  content,
@@ -3165,7 +3229,7 @@ class BedrockAdapter extends BaseProviderAdapter {
3165
3229
  const last = content[content.length - 1];
3166
3230
  return (!!last &&
3167
3231
  "toolUse" in last &&
3168
- last.toolUse.name === STRUCTURED_OUTPUT_TOOL_NAME$2);
3232
+ last.toolUse.name === STRUCTURED_OUTPUT_TOOL_NAME$3);
3169
3233
  }
3170
3234
  extractStructuredOutput(response) {
3171
3235
  const bedrockResponse = response;
@@ -3183,7 +3247,7 @@ class BedrockAdapter extends BaseProviderAdapter {
3183
3247
  const last = content[content.length - 1];
3184
3248
  if (last &&
3185
3249
  "toolUse" in last &&
3186
- last.toolUse.name === STRUCTURED_OUTPUT_TOOL_NAME$2) {
3250
+ last.toolUse.name === STRUCTURED_OUTPUT_TOOL_NAME$3) {
3187
3251
  return last.toolUse.input;
3188
3252
  }
3189
3253
  return undefined;
@@ -3212,123 +3276,965 @@ const bedrockAdapter = new BedrockAdapter();
3212
3276
  //
3213
3277
  // Constants
3214
3278
  //
3215
- const STRUCTURED_OUTPUT_TOOL_NAME$1 = "structured_output";
3216
- // Gemini 3 family supports combining tools (function calling) with native
3217
- // structured output via `responseJsonSchema`. Earlier Gemini families
3218
- // (including 2.5 thinking) do not support the combo and fall back to the
3219
- // legacy `structured_output` fake-tool emulation.
3220
- const GEMINI_3_PATTERN = /^gemini-3/;
3221
- // Reasoning-effort control differs by generation: Gemini 3.x takes the
3222
- // `thinkingLevel` enum, Gemini 2.5 takes an integer `thinkingBudget`. Sending
3223
- // the wrong one errors, and models outside these families have no thinking
3224
- // control, so effort is only applied when one of these matches.
3225
- const GEMINI_25_PATTERN = /^gemini-2\.5/;
3279
+ const STRUCTURED_OUTPUT_TOOL_NAME$2 = "structured_output";
3280
+ const STRUCTURED_OUTPUT_SCHEMA_NAME$1 = "response";
3226
3281
  /**
3227
- * Detect 4xx errors that indicate the model itself does not support the
3228
- * `responseJsonSchema` + tools combo. Triggers the runtime fallback to the
3229
- * fake-tool emulation path. Other 400s propagate.
3282
+ * Detect 4xx errors that indicate the model itself does not support
3283
+ * `response_format: json_schema`. We only trigger the fake-tool fallback when
3284
+ * the failure is plausibly a capability gap, not a generic 400.
3230
3285
  */
3231
- function isStructuredOutputComboUnsupportedError(error) {
3286
+ function isStructuredOutputUnsupportedError$1(error) {
3232
3287
  if (!error || typeof error !== "object")
3233
3288
  return false;
3234
3289
  const err = error;
3235
- const status = err.status ?? err.code;
3290
+ const status = err.status ?? err.statusCode;
3291
+ if (status !== 400 && status !== 422)
3292
+ return false;
3293
+ const messages = [err.message, err.error?.message].filter((m) => typeof m === "string");
3294
+ return messages.some((m) => /response[_ ]format|json[_ ]schema|structured[_ ]output/i.test(m));
3295
+ }
3296
+ function isTemperatureDeprecationError$2(error) {
3297
+ if (!error || typeof error !== "object")
3298
+ return false;
3299
+ const err = error;
3300
+ const status = err.status ?? err.statusCode;
3236
3301
  if (status !== 400)
3237
3302
  return false;
3238
3303
  const messages = [err.message, err.error?.message].filter((m) => typeof m === "string");
3239
- return messages.some((m) => /response[_ ]?json[_ ]?schema|response[_ ]?schema|response[_ ]?mime|function[_ ]?call|tools/i.test(m));
3304
+ return messages.some((m) => m.toLowerCase().includes("temperature"));
3305
+ }
3306
+ /**
3307
+ * Convert standardized content items to Fireworks format. Images become
3308
+ * `image_url` parts (vision models only; non-vision models 4xx — a
3309
+ * model-capability mismatch surfaced by the call). File inputs are warned
3310
+ * and discarded: Fireworks has no `file` part and rejects document `data:`
3311
+ * URIs outright, so there is no wire shape that can carry them.
3312
+ */
3313
+ function convertContentToFireworks(content) {
3314
+ if (typeof content === "string") {
3315
+ return content;
3316
+ }
3317
+ const parts = [];
3318
+ for (const item of content) {
3319
+ if (item.type === exports.LlmMessageType.InputText) {
3320
+ parts.push({ type: "text", text: item.text });
3321
+ continue;
3322
+ }
3323
+ if (item.type === exports.LlmMessageType.InputImage) {
3324
+ const url = item.image_url ?? "";
3325
+ if (!url) {
3326
+ log$1.log.warn("Fireworks image content missing image_url; image discarded");
3327
+ continue;
3328
+ }
3329
+ parts.push({ type: "image_url", imageUrl: { url } });
3330
+ continue;
3331
+ }
3332
+ if (item.type === exports.LlmMessageType.InputFile) {
3333
+ log$1.log.warn({ filename: item.filename }, "Fireworks does not support file inputs (no file part; document data: URIs rejected); file discarded");
3334
+ continue;
3335
+ }
3336
+ // Unknown type - warn and skip
3337
+ log$1.log.warn({ item }, "Unknown content type for Fireworks; discarded");
3338
+ }
3339
+ // If no parts remain, return empty string to avoid empty array
3340
+ if (parts.length === 0) {
3341
+ return "";
3342
+ }
3343
+ return parts;
3344
+ }
3345
+ /**
3346
+ * Convert internal content parts to the OpenAI-compatible wire shape. The
3347
+ * internal representation uses camelCase keys (`imageUrl`); the REST API wants
3348
+ * snake_case (`image_url`).
3349
+ */
3350
+ function contentToWire$1(content) {
3351
+ if (content === null ||
3352
+ content === undefined ||
3353
+ typeof content === "string") {
3354
+ return content;
3355
+ }
3356
+ return content.map((part) => {
3357
+ if (part.type === "image_url") {
3358
+ return { type: "image_url", image_url: part.imageUrl };
3359
+ }
3360
+ return part;
3361
+ });
3362
+ }
3363
+ /**
3364
+ * Serialize an internal message to the OpenAI-compatible wire shape, mapping
3365
+ * camelCase tool fields (`toolCalls`, `toolCallId`) to snake_case. Tool-call
3366
+ * objects are already wire-shaped (`{ id, type, function: { name, arguments } }`).
3367
+ */
3368
+ function messageToWire$1(message) {
3369
+ const wire = { role: message.role };
3370
+ if (message.content !== undefined) {
3371
+ wire.content = contentToWire$1(message.content);
3372
+ }
3373
+ if (message.toolCalls) {
3374
+ wire.tool_calls = message.toolCalls;
3375
+ }
3376
+ if (message.toolCallId) {
3377
+ wire.tool_call_id = message.toolCallId;
3378
+ }
3379
+ return wire;
3380
+ }
3381
+ // Fireworks error types based on HTTP status codes
3382
+ const RETRYABLE_STATUS_CODES$2 = [408, 500, 502, 503, 524, 529];
3383
+ const RATE_LIMIT_STATUS_CODE$1 = 429;
3384
+ /**
3385
+ * Walk the JSON schema and force `additionalProperties: false` on every
3386
+ * object node. Required by the OpenAI-style json_schema response_format when
3387
+ * `strict: true`.
3388
+ */
3389
+ function enforceAdditionalPropertiesFalse$1(schema) {
3390
+ const stack = [schema];
3391
+ while (stack.length > 0) {
3392
+ const node = stack.pop();
3393
+ if (node.type === "object") {
3394
+ node.additionalProperties = false;
3395
+ }
3396
+ for (const value of Object.values(node)) {
3397
+ if (Array.isArray(value)) {
3398
+ for (const entry of value) {
3399
+ if (entry && typeof entry === "object" && !Array.isArray(entry)) {
3400
+ stack.push(entry);
3401
+ }
3402
+ }
3403
+ }
3404
+ else if (value && typeof value === "object") {
3405
+ stack.push(value);
3406
+ }
3407
+ }
3408
+ }
3240
3409
  }
3241
- // Gemini uses HTTP status codes for error classification
3242
- // Documented at: https://ai.google.dev/api/rest/v1beta/Status
3243
- const RETRYABLE_STATUS_CODES$1 = [
3244
- 408, // Request Timeout
3245
- 429, // Too Many Requests (Rate Limit)
3246
- 500, // Internal Server Error
3247
- 502, // Bad Gateway
3248
- 503, // Service Unavailable
3249
- 504, // Gateway Timeout
3250
- ];
3251
- const NOT_RETRYABLE_STATUS_CODES = [
3252
- 400, // Bad Request
3253
- 401, // Unauthorized
3254
- 403, // Forbidden
3255
- 404, // Not Found
3256
- 409, // Conflict
3257
- 422, // Unprocessable Entity
3258
- ];
3259
3410
  //
3260
3411
  //
3261
3412
  // Main
3262
3413
  //
3263
3414
  /**
3264
- * GoogleAdapter implements the ProviderAdapter interface for Google's Gemini API.
3265
- * It handles request building, response parsing, and error classification
3266
- * specific to Gemini's generateContent API.
3415
+ * FireworksAdapter implements the ProviderAdapter interface for Fireworks AI's
3416
+ * OpenAI-compatible Chat Completions API. It handles request building,
3417
+ * response parsing, and error classification specific to Fireworks.
3267
3418
  */
3268
- class GoogleAdapter extends BaseProviderAdapter {
3419
+ class FireworksAdapter extends BaseProviderAdapter {
3269
3420
  constructor() {
3270
3421
  super(...arguments);
3271
- this.name = PROVIDER.GOOGLE.NAME;
3272
- this.defaultModel = PROVIDER.GOOGLE.DEFAULT;
3273
- // Session-level cache of Gemini 3 models observed to reject the native
3274
- // `responseJsonSchema` + tools combo. When a model is in this set,
3275
- // buildRequest engages the legacy fake-tool path instead.
3276
- this.runtimeNoStructuredOutputComboModels = new Set();
3422
+ this.name = PROVIDER.FIREWORKS.NAME;
3423
+ this.defaultModel = PROVIDER.FIREWORKS.DEFAULT;
3424
+ // Structured output with tools rides the structured_output tool emulation
3425
+ // (Fireworks rejects response_format + tools), and emulation compliance is
3426
+ // a model decision opt in to OperateLoop's corrective retry turn.
3427
+ this.supportsStructuredOutputRetry = true;
3428
+ // Session-level cache of models observed to reject native
3429
+ // `response_format: json_schema`. When a model is in this set, buildRequest
3430
+ // engages the legacy fake-tool path instead of native structured output.
3431
+ this.runtimeNoStructuredOutputModels = new Set();
3432
+ // Session-level cache of models observed to reject `temperature`. Populated
3433
+ // by executeRequest on 400 errors so repeat calls skip the param.
3434
+ this.runtimeNoTemperatureModels = new Set();
3277
3435
  }
3278
- rememberModelRejectsStructuredOutputCombo(model) {
3279
- this.runtimeNoStructuredOutputComboModels.add(model);
3436
+ rememberModelRejectsStructuredOutput(model) {
3437
+ this.runtimeNoStructuredOutputModels.add(model);
3280
3438
  }
3281
- clearRuntimeNoStructuredOutputComboModels() {
3282
- this.runtimeNoStructuredOutputComboModels.clear();
3439
+ clearRuntimeNoStructuredOutputModels() {
3440
+ this.runtimeNoStructuredOutputModels.clear();
3283
3441
  }
3284
- supportsStructuredOutputCombo(model) {
3285
- if (this.runtimeNoStructuredOutputComboModels.has(model))
3286
- return false;
3287
- return GEMINI_3_PATTERN.test(model);
3442
+ supportsStructuredOutput(model) {
3443
+ return !this.runtimeNoStructuredOutputModels.has(model);
3444
+ }
3445
+ rememberModelRejectsTemperature(model) {
3446
+ this.runtimeNoTemperatureModels.add(model);
3447
+ }
3448
+ clearRuntimeNoTemperatureModels() {
3449
+ this.runtimeNoTemperatureModels.clear();
3450
+ }
3451
+ supportsTemperature(model) {
3452
+ return !this.runtimeNoTemperatureModels.has(model);
3288
3453
  }
3289
3454
  //
3290
3455
  // Request Building
3291
3456
  //
3292
3457
  buildRequest(request) {
3293
- // Convert messages to Gemini format (Content[])
3294
- const contents = this.convertMessagesToContents(request.messages);
3295
- const geminiRequest = {
3458
+ // Convert messages to Fireworks format (OpenAI-compatible)
3459
+ const messages = this.convertMessagesToFireworks(request.messages, request.system);
3460
+ // Append instructions to last message if provided
3461
+ if (request.instructions && messages.length > 0) {
3462
+ const lastMsg = messages[messages.length - 1];
3463
+ if (lastMsg.content && typeof lastMsg.content === "string") {
3464
+ lastMsg.content = lastMsg.content + "\n\n" + request.instructions;
3465
+ }
3466
+ }
3467
+ const fireworksRequest = {
3296
3468
  model: request.model || this.defaultModel,
3297
- contents,
3469
+ messages,
3298
3470
  };
3299
- // Add system instruction if provided
3300
- if (request.system) {
3301
- geminiRequest.config = {
3302
- ...geminiRequest.config,
3303
- systemInstruction: request.system,
3304
- };
3305
- }
3306
- // Append instructions to the last user message if provided
3307
- if (request.instructions && contents.length > 0) {
3308
- const lastContent = contents[contents.length - 1];
3309
- if (lastContent.role === "user" && lastContent.parts) {
3310
- const lastPart = lastContent.parts[lastContent.parts.length - 1];
3311
- if (lastPart.text) {
3312
- lastPart.text = lastPart.text + "\n\n" + request.instructions;
3313
- }
3314
- }
3471
+ if (request.user) {
3472
+ fireworksRequest.user = request.user;
3315
3473
  }
3316
- const hasUserTools = !!(request.tools && request.tools.length > 0);
3317
- const useNativeCombo = Boolean(request.format) &&
3318
- hasUserTools &&
3319
- this.supportsStructuredOutputCombo(geminiRequest.model);
3320
- // When tools+format are combined and the model does not support the native
3321
- // combo, inject the legacy `structured_output` fake tool here so the model
3322
- // is forced to call it before its final answer.
3323
- const allTools = request.tools
3324
- ? [...request.tools]
3325
- : [];
3326
- if (request.format && hasUserTools && !useNativeCombo) {
3327
- log$1.log.warn(`[GoogleAdapter] Using legacy structured_output tool fallback for model ${geminiRequest.model}; native responseJsonSchema + tools combo is only available on Gemini 3.`);
3474
+ // Fireworks rejects `response_format` combined with `tools` ("You cannot
3475
+ // specify response format and function call at the same time"), so a
3476
+ // request with both preemptively uses the structured_output tool
3477
+ // emulation — same approach as Gemini 2.5.
3478
+ const hasCallerTools = Boolean(request.tools?.length);
3479
+ const useFallbackStructuredOutput = Boolean(request.format) &&
3480
+ (hasCallerTools ||
3481
+ !this.supportsStructuredOutput(fireworksRequest.model));
3482
+ // On a corrective retry turn (the model answered a format request with
3483
+ // prose), offer only the structured_output tool so the demanded call is
3484
+ // the sole option.
3485
+ const allTools = request.tools && !request.structuredOutputRetry ? [...request.tools] : [];
3486
+ if (useFallbackStructuredOutput && request.format) {
3487
+ log$1.log.warn(hasCallerTools
3488
+ ? `[FireworksAdapter] Fireworks does not support response_format combined with tools; using structured_output tool emulation for model ${fireworksRequest.model}.`
3489
+ : `[FireworksAdapter] Using legacy structured_output tool fallback for model ${fireworksRequest.model}; native response_format previously rejected for this model.`);
3328
3490
  allTools.push({
3329
- name: STRUCTURED_OUTPUT_TOOL_NAME$1,
3330
- description: "Output a structured JSON object, " +
3331
- "use this before your final response to give structured outputs to the user",
3491
+ name: STRUCTURED_OUTPUT_TOOL_NAME$2,
3492
+ description: "REQUIRED: You MUST call this tool to provide your final response. " +
3493
+ "After gathering all necessary information (including results from other tools), " +
3494
+ "call this tool with the structured data to complete the request.",
3495
+ parameters: request.format,
3496
+ });
3497
+ }
3498
+ if (allTools.length > 0) {
3499
+ fireworksRequest.tools = allTools.map((tool) => ({
3500
+ type: "function",
3501
+ function: {
3502
+ name: tool.name,
3503
+ description: tool.description,
3504
+ parameters: tool.parameters,
3505
+ },
3506
+ }));
3507
+ // Use "auto" for tool_choice - not all Fireworks models support "required"
3508
+ // The structured_output tool prompt already emphasizes it must be called
3509
+ fireworksRequest.tool_choice = "auto";
3510
+ }
3511
+ // Native structured output: send schema as `response_format`. The legacy
3512
+ // tool-emulation path is engaged only as a runtime fallback for models the
3513
+ // API has flagged as not supporting native json_schema.
3514
+ if (request.format && !useFallbackStructuredOutput) {
3515
+ fireworksRequest.response_format = {
3516
+ type: "json_schema",
3517
+ json_schema: {
3518
+ name: STRUCTURED_OUTPUT_SCHEMA_NAME$1,
3519
+ schema: request.format,
3520
+ strict: true,
3521
+ },
3522
+ };
3523
+ }
3524
+ if (request.providerOptions) {
3525
+ Object.assign(fireworksRequest, request.providerOptions);
3526
+ }
3527
+ // Normalized reasoning effort -> reasoning_effort. Fireworks accepts the
3528
+ // param on every model and no-ops where unsupported, so no per-model
3529
+ // gating. First-class effort wins over providerOptions.
3530
+ if (request.effort) {
3531
+ const mapping = toFireworksEffort(request.effort);
3532
+ if (mapping.papered) {
3533
+ log$1.log.debug(paperedEffortMessage({
3534
+ model: fireworksRequest.model,
3535
+ provider: this.name,
3536
+ requested: request.effort,
3537
+ value: mapping.value,
3538
+ }));
3539
+ }
3540
+ fireworksRequest.reasoning_effort = mapping.value;
3541
+ }
3542
+ // First-class temperature takes precedence over providerOptions
3543
+ if (request.temperature !== undefined) {
3544
+ fireworksRequest.temperature =
3545
+ request.temperature;
3546
+ }
3547
+ // Strip temperature for models the runtime has cached as rejecting it
3548
+ const requestRecord = fireworksRequest;
3549
+ if (requestRecord.temperature !== undefined &&
3550
+ !this.supportsTemperature(fireworksRequest.model)) {
3551
+ delete requestRecord.temperature;
3552
+ }
3553
+ return fireworksRequest;
3554
+ }
3555
+ formatTools(toolkit,
3556
+ // outputSchema is part of the interface contract but Fireworks uses native
3557
+ // `response_format` (set in buildRequest); the legacy fake-tool injection
3558
+ // happens in buildRequest only as a runtime fallback.
3559
+ _outputSchema) {
3560
+ return toolkit.tools.map((tool) => ({
3561
+ name: tool.name,
3562
+ description: tool.description,
3563
+ parameters: tool.parameters,
3564
+ }));
3565
+ }
3566
+ formatOutputSchema(schema) {
3567
+ let jsonSchema;
3568
+ // Check if schema is already a JsonObject — either the OpenAI-style
3569
+ // `{ type: "json_schema", ... }` envelope or a bare `{ type: "object", properties }` node
3570
+ if ((typeof schema === "object" &&
3571
+ schema !== null &&
3572
+ !Array.isArray(schema) &&
3573
+ schema.type === "json_schema") ||
3574
+ isJsonSchema$1(schema)) {
3575
+ jsonSchema = structuredClone(schema);
3576
+ jsonSchema.type = "object"; // Normalize type
3577
+ }
3578
+ else {
3579
+ // Convert NaturalSchema to JSON schema through Zod. Re-spread into a
3580
+ // plain object to drop Zod v4's non-enumerable `~standard` marker.
3581
+ const zodSchema = schema instanceof v4.z.ZodType
3582
+ ? schema
3583
+ : naturalZodSchema(schema);
3584
+ jsonSchema = { ...v4.z.toJSONSchema(zodSchema) };
3585
+ }
3586
+ // Remove $schema property (can cause issues with some providers)
3587
+ if (jsonSchema.$schema) {
3588
+ delete jsonSchema.$schema;
3589
+ }
3590
+ // Strict json_schema response_format requires additionalProperties: false
3591
+ // on every object.
3592
+ enforceAdditionalPropertiesFalse$1(jsonSchema);
3593
+ return jsonSchema;
3594
+ }
3595
+ //
3596
+ // API Execution
3597
+ //
3598
+ async executeRequest(client, request, signal) {
3599
+ const fireworks = client;
3600
+ const fireworksRequest = request;
3601
+ const wantsStructuredOutput = Boolean(fireworksRequest.response_format);
3602
+ try {
3603
+ const response = (await fireworks.chatCompletion(this.toWireBody(fireworksRequest), signal ? { signal } : undefined));
3604
+ if (wantsStructuredOutput) {
3605
+ response.__jaypieStructuredOutput = true;
3606
+ }
3607
+ return response;
3608
+ }
3609
+ catch (error) {
3610
+ if (signal?.aborted)
3611
+ return undefined;
3612
+ // If the model rejected `temperature`, cache it and retry without the param.
3613
+ const requestRecord = fireworksRequest;
3614
+ if (requestRecord.temperature !== undefined &&
3615
+ isTemperatureDeprecationError$2(error)) {
3616
+ this.rememberModelRejectsTemperature(fireworksRequest.model);
3617
+ const retryRequest = { ...fireworksRequest };
3618
+ delete retryRequest.temperature;
3619
+ const response = (await fireworks.chatCompletion(this.toWireBody(retryRequest), signal ? { signal } : undefined));
3620
+ if (wantsStructuredOutput) {
3621
+ response.__jaypieStructuredOutput = true;
3622
+ }
3623
+ return response;
3624
+ }
3625
+ // If the model rejected `response_format`, cache it and retry with the
3626
+ // legacy fake-tool emulation path.
3627
+ if (wantsStructuredOutput && isStructuredOutputUnsupportedError$1(error)) {
3628
+ const model = fireworksRequest.model;
3629
+ this.rememberModelRejectsStructuredOutput(model);
3630
+ log$1.log.warn(`[FireworksAdapter] Model ${model} rejected native response_format; falling back to legacy structured_output tool emulation.`);
3631
+ const fallbackRequest = this.toFallbackStructuredOutputRequest(fireworksRequest);
3632
+ return (await fireworks.chatCompletion(this.toWireBody(fallbackRequest), signal ? { signal } : undefined));
3633
+ }
3634
+ throw error;
3635
+ }
3636
+ }
3637
+ /**
3638
+ * Serialize the internal request into the OpenAI-compatible wire body for
3639
+ * Fireworks' Chat Completions endpoint. Top-level fields (model, tools,
3640
+ * tool_choice, response_format, reasoning_effort, user, temperature, and any
3641
+ * providerOptions) are already wire-shaped (snake_case); only messages carry
3642
+ * camelCase tool fields that must become snake_case on the wire.
3643
+ */
3644
+ toWireBody(fireworksRequest) {
3645
+ return {
3646
+ ...fireworksRequest,
3647
+ messages: fireworksRequest.messages.map(messageToWire$1),
3648
+ };
3649
+ }
3650
+ /**
3651
+ * Rebuild a structured-output request without `response_format`, swapping in
3652
+ * the legacy fake-tool emulation. Used as a runtime fallback when a model
3653
+ * rejects native json_schema.
3654
+ */
3655
+ toFallbackStructuredOutputRequest(request) {
3656
+ if (!request.response_format ||
3657
+ request.response_format.type !== "json_schema") {
3658
+ return request;
3659
+ }
3660
+ const { response_format, ...rest } = request;
3661
+ const fallbackRequest = { ...rest };
3662
+ const schema = response_format.json_schema.schema;
3663
+ const fakeTool = {
3664
+ type: "function",
3665
+ function: {
3666
+ name: STRUCTURED_OUTPUT_TOOL_NAME$2,
3667
+ description: "REQUIRED: You MUST call this tool to provide your final response. " +
3668
+ "After gathering all necessary information (including results from other tools), " +
3669
+ "call this tool with the structured data to complete the request.",
3670
+ parameters: schema,
3671
+ },
3672
+ };
3673
+ fallbackRequest.tools = [...(fallbackRequest.tools ?? []), fakeTool];
3674
+ fallbackRequest.tool_choice = "auto";
3675
+ return fallbackRequest;
3676
+ }
3677
+ async *executeStreamRequest(client, request, signal) {
3678
+ const fireworks = client;
3679
+ const fireworksRequest = request;
3680
+ // streamChatCompletion adds `stream: true` + `stream_options.include_usage`
3681
+ // and yields decoded SSE chunks in OpenAI-compatible (snake_case) shape.
3682
+ const stream = fireworks.streamChatCompletion(this.toWireBody(fireworksRequest), signal ? { signal } : undefined);
3683
+ // Track current tool call being built
3684
+ let currentToolCall = null;
3685
+ // Track usage for final chunk
3686
+ let inputTokens = 0;
3687
+ let outputTokens = 0;
3688
+ const model = fireworksRequest.model || this.defaultModel;
3689
+ for await (const chunk of stream) {
3690
+ const typedChunk = chunk;
3691
+ const choices = typedChunk.choices;
3692
+ if (choices && choices.length > 0) {
3693
+ const delta = choices[0].delta;
3694
+ // Handle text content
3695
+ if (delta?.content) {
3696
+ yield {
3697
+ type: exports.LlmStreamChunkType.Text,
3698
+ content: delta.content,
3699
+ };
3700
+ }
3701
+ // Handle tool calls
3702
+ if (delta?.tool_calls && delta.tool_calls.length > 0) {
3703
+ for (const toolCallDelta of delta.tool_calls) {
3704
+ if (toolCallDelta.id) {
3705
+ // New tool call starting
3706
+ if (currentToolCall) {
3707
+ // Emit the previous tool call
3708
+ yield {
3709
+ type: exports.LlmStreamChunkType.ToolCall,
3710
+ toolCall: {
3711
+ id: currentToolCall.id,
3712
+ name: currentToolCall.name,
3713
+ arguments: currentToolCall.arguments,
3714
+ },
3715
+ };
3716
+ }
3717
+ currentToolCall = {
3718
+ id: toolCallDelta.id,
3719
+ name: toolCallDelta.function?.name || "",
3720
+ arguments: toolCallDelta.function?.arguments || "",
3721
+ };
3722
+ }
3723
+ else if (currentToolCall) {
3724
+ // Continuing existing tool call
3725
+ if (toolCallDelta.function?.name) {
3726
+ currentToolCall.name += toolCallDelta.function.name;
3727
+ }
3728
+ if (toolCallDelta.function?.arguments) {
3729
+ currentToolCall.arguments += toolCallDelta.function.arguments;
3730
+ }
3731
+ }
3732
+ }
3733
+ }
3734
+ // Check for finish reason
3735
+ if (choices[0].finish_reason) {
3736
+ // Emit any pending tool call
3737
+ if (currentToolCall) {
3738
+ yield {
3739
+ type: exports.LlmStreamChunkType.ToolCall,
3740
+ toolCall: {
3741
+ id: currentToolCall.id,
3742
+ name: currentToolCall.name,
3743
+ arguments: currentToolCall.arguments,
3744
+ },
3745
+ };
3746
+ currentToolCall = null;
3747
+ }
3748
+ }
3749
+ }
3750
+ // Extract usage if present (usually in the final chunk)
3751
+ if (typedChunk.usage) {
3752
+ inputTokens =
3753
+ typedChunk.usage.prompt_tokens || typedChunk.usage.promptTokens || 0;
3754
+ outputTokens =
3755
+ typedChunk.usage.completion_tokens ||
3756
+ typedChunk.usage.completionTokens ||
3757
+ 0;
3758
+ }
3759
+ }
3760
+ // Emit done chunk with final usage
3761
+ yield {
3762
+ type: exports.LlmStreamChunkType.Done,
3763
+ usage: [
3764
+ {
3765
+ input: inputTokens,
3766
+ output: outputTokens,
3767
+ reasoning: 0,
3768
+ total: inputTokens + outputTokens,
3769
+ provider: this.name,
3770
+ model,
3771
+ },
3772
+ ],
3773
+ };
3774
+ }
3775
+ //
3776
+ // Response Parsing
3777
+ //
3778
+ parseResponse(response, _options) {
3779
+ const fireworksResponse = response;
3780
+ const choice = fireworksResponse.choices[0];
3781
+ const content = this.extractContent(fireworksResponse);
3782
+ const hasToolCalls = this.hasToolCalls(fireworksResponse);
3783
+ const stopReason = choice?.finishReason ?? choice?.finish_reason ?? undefined;
3784
+ return {
3785
+ content,
3786
+ hasToolCalls,
3787
+ stopReason,
3788
+ usage: this.extractUsage(fireworksResponse, fireworksResponse.model),
3789
+ raw: fireworksResponse,
3790
+ };
3791
+ }
3792
+ extractToolCalls(response) {
3793
+ const fireworksResponse = response;
3794
+ const toolCalls = [];
3795
+ const choice = fireworksResponse.choices[0];
3796
+ if (!choice?.message?.toolCalls) {
3797
+ return toolCalls;
3798
+ }
3799
+ for (const toolCall of choice.message.toolCalls) {
3800
+ toolCalls.push({
3801
+ callId: toolCall.id,
3802
+ name: toolCall.function.name,
3803
+ arguments: toolCall.function.arguments,
3804
+ raw: toolCall,
3805
+ });
3806
+ }
3807
+ return toolCalls;
3808
+ }
3809
+ extractUsage(response, model) {
3810
+ const fireworksResponse = response;
3811
+ if (!fireworksResponse.usage) {
3812
+ return {
3813
+ input: 0,
3814
+ output: 0,
3815
+ reasoning: 0,
3816
+ total: 0,
3817
+ provider: this.name,
3818
+ model,
3819
+ };
3820
+ }
3821
+ const usage = fireworksResponse.usage;
3822
+ return {
3823
+ input: usage.promptTokens || usage.prompt_tokens || 0,
3824
+ output: usage.completionTokens || usage.completion_tokens || 0,
3825
+ reasoning: usage.completionTokensDetails?.reasoningTokens || 0,
3826
+ total: usage.totalTokens || usage.total_tokens || 0,
3827
+ provider: this.name,
3828
+ model,
3829
+ };
3830
+ }
3831
+ //
3832
+ // Tool Result Handling
3833
+ //
3834
+ formatToolResult(toolCall, result) {
3835
+ return {
3836
+ role: "tool",
3837
+ toolCallId: toolCall.callId,
3838
+ content: result.output,
3839
+ };
3840
+ }
3841
+ appendToolResult(request, toolCall, result) {
3842
+ const fireworksRequest = request;
3843
+ const toolCallRaw = toolCall.raw;
3844
+ // Add assistant message with the tool call
3845
+ fireworksRequest.messages.push({
3846
+ role: "assistant",
3847
+ content: null,
3848
+ toolCalls: [toolCallRaw],
3849
+ });
3850
+ // Add tool result message
3851
+ fireworksRequest.messages.push(this.formatToolResult(toolCall, result));
3852
+ return fireworksRequest;
3853
+ }
3854
+ //
3855
+ // History Management
3856
+ //
3857
+ responseToHistoryItems(response) {
3858
+ const fireworksResponse = response;
3859
+ const historyItems = [];
3860
+ const choice = fireworksResponse.choices[0];
3861
+ if (!choice?.message) {
3862
+ return historyItems;
3863
+ }
3864
+ // Check if this is a tool use response
3865
+ if (choice.message.toolCalls && choice.message.toolCalls.length > 0) {
3866
+ // Don't add to history yet - will be added after tool execution
3867
+ return historyItems;
3868
+ }
3869
+ // Extract text content for non-tool responses
3870
+ if (choice.message.content) {
3871
+ const historyItem = {
3872
+ content: choice.message.content,
3873
+ role: exports.LlmMessageRole.Assistant,
3874
+ type: exports.LlmMessageType.Message,
3875
+ };
3876
+ // Preserve reasoning if present. Fireworks reasoning models return it in
3877
+ // reasoning_content; some routes use reasoning.
3878
+ const reasoning = choice.message.reasoning_content ?? choice.message.reasoning;
3879
+ if (reasoning) {
3880
+ historyItem.reasoning = reasoning;
3881
+ }
3882
+ historyItems.push(historyItem);
3883
+ }
3884
+ return historyItems;
3885
+ }
3886
+ //
3887
+ // Error Classification
3888
+ //
3889
+ classifyError(error) {
3890
+ // Shared first pass: retryable structured-output timeouts (#422),
3891
+ // quota exhaustion, and billing failures classify the same across providers.
3892
+ const shared = classifyProviderError(error);
3893
+ if (shared)
3894
+ return shared;
3895
+ // Check if error has a status code (HTTP error)
3896
+ const errorWithStatus = error;
3897
+ const statusCode = errorWithStatus.status || errorWithStatus.statusCode;
3898
+ if (statusCode) {
3899
+ // Rate limit error
3900
+ if (statusCode === RATE_LIMIT_STATUS_CODE$1) {
3901
+ return {
3902
+ error,
3903
+ category: exports.ErrorCategory.RateLimit,
3904
+ shouldRetry: false,
3905
+ suggestedDelayMs: 60000,
3906
+ };
3907
+ }
3908
+ // Retryable errors (server errors, timeouts, etc.)
3909
+ if (RETRYABLE_STATUS_CODES$2.includes(statusCode)) {
3910
+ return {
3911
+ error,
3912
+ category: exports.ErrorCategory.Retryable,
3913
+ shouldRetry: true,
3914
+ };
3915
+ }
3916
+ // Client errors (4xx except 429) are unrecoverable
3917
+ if (statusCode >= 400 && statusCode < 500) {
3918
+ return {
3919
+ error,
3920
+ category: exports.ErrorCategory.Unrecoverable,
3921
+ shouldRetry: false,
3922
+ };
3923
+ }
3924
+ }
3925
+ // Check error message for rate limit indicators
3926
+ const errorMessage = error instanceof Error ? error.message.toLowerCase() : "";
3927
+ if (errorMessage.includes("rate limit") ||
3928
+ errorMessage.includes("too many requests")) {
3929
+ return {
3930
+ error,
3931
+ category: exports.ErrorCategory.RateLimit,
3932
+ shouldRetry: false,
3933
+ suggestedDelayMs: 60000,
3934
+ };
3935
+ }
3936
+ // Check for transient network errors (ECONNRESET, etc.)
3937
+ if (isTransientNetworkError(error)) {
3938
+ return {
3939
+ error,
3940
+ category: exports.ErrorCategory.Retryable,
3941
+ shouldRetry: true,
3942
+ };
3943
+ }
3944
+ // Unknown error - treat as potentially retryable
3945
+ return {
3946
+ error,
3947
+ category: exports.ErrorCategory.Unknown,
3948
+ shouldRetry: true,
3949
+ };
3950
+ }
3951
+ //
3952
+ // Provider-Specific Features
3953
+ //
3954
+ isComplete(response) {
3955
+ const fireworksResponse = response;
3956
+ const choice = fireworksResponse.choices[0];
3957
+ // Complete if no tool calls
3958
+ if (!choice?.message?.toolCalls?.length) {
3959
+ return true;
3960
+ }
3961
+ return false;
3962
+ }
3963
+ hasStructuredOutput(response) {
3964
+ const fireworksResponse = response;
3965
+ // Native path: executeRequest annotates the response when we sent
3966
+ // `response_format`, so we can detect intent statelessly.
3967
+ if (fireworksResponse.__jaypieStructuredOutput) {
3968
+ return this.extractStructuredOutput(response) !== undefined;
3969
+ }
3970
+ // Fallback path: legacy fake-tool emulation, kept for models that the
3971
+ // runtime has cached as not supporting native `response_format`.
3972
+ const choice = fireworksResponse.choices[0];
3973
+ if (!choice?.message?.toolCalls?.length) {
3974
+ return false;
3975
+ }
3976
+ // Check if the last tool call is structured_output
3977
+ const lastToolCall = choice.message.toolCalls[choice.message.toolCalls.length - 1];
3978
+ return lastToolCall?.function?.name === STRUCTURED_OUTPUT_TOOL_NAME$2;
3979
+ }
3980
+ extractStructuredOutput(response) {
3981
+ const fireworksResponse = response;
3982
+ if (fireworksResponse.__jaypieStructuredOutput) {
3983
+ const choice = fireworksResponse.choices[0];
3984
+ const content = choice?.message?.content;
3985
+ if (typeof content !== "string" || content.length === 0) {
3986
+ return undefined;
3987
+ }
3988
+ try {
3989
+ return JSON.parse(content);
3990
+ }
3991
+ catch {
3992
+ return undefined;
3993
+ }
3994
+ }
3995
+ // Fallback path: legacy fake-tool emulation
3996
+ const choice = fireworksResponse.choices[0];
3997
+ if (!choice?.message?.toolCalls?.length) {
3998
+ return undefined;
3999
+ }
4000
+ const lastToolCall = choice.message.toolCalls[choice.message.toolCalls.length - 1];
4001
+ if (lastToolCall?.function?.name === STRUCTURED_OUTPUT_TOOL_NAME$2) {
4002
+ try {
4003
+ return JSON.parse(lastToolCall.function.arguments);
4004
+ }
4005
+ catch {
4006
+ return undefined;
4007
+ }
4008
+ }
4009
+ return undefined;
4010
+ }
4011
+ //
4012
+ // Private Helpers
4013
+ //
4014
+ hasToolCalls(response) {
4015
+ const choice = response.choices[0];
4016
+ return (choice?.message?.toolCalls?.length ?? 0) > 0;
4017
+ }
4018
+ extractContent(response) {
4019
+ // Check for structured output first
4020
+ if (this.hasStructuredOutput(response)) {
4021
+ return this.extractStructuredOutput(response);
4022
+ }
4023
+ const choice = response.choices[0];
4024
+ return choice?.message?.content ?? undefined;
4025
+ }
4026
+ convertMessagesToFireworks(messages, system) {
4027
+ const fireworksMessages = [];
4028
+ // Add system message if provided
4029
+ if (system) {
4030
+ fireworksMessages.push({
4031
+ role: "system",
4032
+ content: system,
4033
+ });
4034
+ }
4035
+ for (const msg of messages) {
4036
+ const message = msg;
4037
+ // Handle different message types
4038
+ if (message.role === "system") {
4039
+ fireworksMessages.push({
4040
+ role: "system",
4041
+ content: message.content,
4042
+ });
4043
+ }
4044
+ else if (message.role === "user") {
4045
+ fireworksMessages.push({
4046
+ role: "user",
4047
+ content: convertContentToFireworks(message.content),
4048
+ });
4049
+ }
4050
+ else if (message.role === "assistant") {
4051
+ const assistantMsg = {
4052
+ role: "assistant",
4053
+ content: message.content || null,
4054
+ };
4055
+ // Include toolCalls if present (check both camelCase and snake_case for compatibility)
4056
+ if (message.toolCalls) {
4057
+ assistantMsg.toolCalls = message.toolCalls;
4058
+ }
4059
+ else if (message.tool_calls) {
4060
+ assistantMsg.toolCalls = message.tool_calls;
4061
+ }
4062
+ fireworksMessages.push(assistantMsg);
4063
+ }
4064
+ else if (message.role === "tool") {
4065
+ fireworksMessages.push({
4066
+ role: "tool",
4067
+ toolCallId: message.toolCallId || message.tool_call_id,
4068
+ content: message.content,
4069
+ });
4070
+ }
4071
+ else if (message.type === exports.LlmMessageType.Message) {
4072
+ // Handle internal message format
4073
+ const role = message.role?.toLowerCase();
4074
+ if (role === "assistant") {
4075
+ fireworksMessages.push({
4076
+ role: "assistant",
4077
+ content: message.content,
4078
+ });
4079
+ }
4080
+ else {
4081
+ fireworksMessages.push({
4082
+ role: "user",
4083
+ content: convertContentToFireworks(message.content),
4084
+ });
4085
+ }
4086
+ }
4087
+ else if (message.type === exports.LlmMessageType.FunctionCall) {
4088
+ fireworksMessages.push({
4089
+ role: "assistant",
4090
+ content: null,
4091
+ toolCalls: [
4092
+ {
4093
+ id: message.call_id,
4094
+ type: "function",
4095
+ function: {
4096
+ name: message.name,
4097
+ arguments: message.arguments || "{}",
4098
+ },
4099
+ },
4100
+ ],
4101
+ });
4102
+ }
4103
+ else if (message.type === exports.LlmMessageType.FunctionCallOutput) {
4104
+ fireworksMessages.push({
4105
+ role: "tool",
4106
+ toolCallId: message.call_id,
4107
+ content: message.output || "",
4108
+ });
4109
+ }
4110
+ }
4111
+ return fireworksMessages;
4112
+ }
4113
+ }
4114
+ // Export singleton instance
4115
+ const fireworksAdapter = new FireworksAdapter();
4116
+
4117
+ //
4118
+ //
4119
+ // Constants
4120
+ //
4121
+ const STRUCTURED_OUTPUT_TOOL_NAME$1 = "structured_output";
4122
+ // Gemini 3 family supports combining tools (function calling) with native
4123
+ // structured output via `responseJsonSchema`. Earlier Gemini families
4124
+ // (including 2.5 thinking) do not support the combo and fall back to the
4125
+ // legacy `structured_output` fake-tool emulation.
4126
+ const GEMINI_3_PATTERN = /^gemini-3/;
4127
+ // Reasoning-effort control differs by generation: Gemini 3.x takes the
4128
+ // `thinkingLevel` enum, Gemini 2.5 takes an integer `thinkingBudget`. Sending
4129
+ // the wrong one errors, and models outside these families have no thinking
4130
+ // control, so effort is only applied when one of these matches.
4131
+ const GEMINI_25_PATTERN = /^gemini-2\.5/;
4132
+ /**
4133
+ * Detect 4xx errors that indicate the model itself does not support the
4134
+ * `responseJsonSchema` + tools combo. Triggers the runtime fallback to the
4135
+ * fake-tool emulation path. Other 400s propagate.
4136
+ */
4137
+ function isStructuredOutputComboUnsupportedError(error) {
4138
+ if (!error || typeof error !== "object")
4139
+ return false;
4140
+ const err = error;
4141
+ const status = err.status ?? err.code;
4142
+ if (status !== 400)
4143
+ return false;
4144
+ const messages = [err.message, err.error?.message].filter((m) => typeof m === "string");
4145
+ return messages.some((m) => /response[_ ]?json[_ ]?schema|response[_ ]?schema|response[_ ]?mime|function[_ ]?call|tools/i.test(m));
4146
+ }
4147
+ // Gemini uses HTTP status codes for error classification
4148
+ // Documented at: https://ai.google.dev/api/rest/v1beta/Status
4149
+ const RETRYABLE_STATUS_CODES$1 = [
4150
+ 408, // Request Timeout
4151
+ 429, // Too Many Requests (Rate Limit)
4152
+ 500, // Internal Server Error
4153
+ 502, // Bad Gateway
4154
+ 503, // Service Unavailable
4155
+ 504, // Gateway Timeout
4156
+ ];
4157
+ const NOT_RETRYABLE_STATUS_CODES = [
4158
+ 400, // Bad Request
4159
+ 401, // Unauthorized
4160
+ 403, // Forbidden
4161
+ 404, // Not Found
4162
+ 409, // Conflict
4163
+ 422, // Unprocessable Entity
4164
+ ];
4165
+ //
4166
+ //
4167
+ // Main
4168
+ //
4169
+ /**
4170
+ * GoogleAdapter implements the ProviderAdapter interface for Google's Gemini API.
4171
+ * It handles request building, response parsing, and error classification
4172
+ * specific to Gemini's generateContent API.
4173
+ */
4174
+ class GoogleAdapter extends BaseProviderAdapter {
4175
+ constructor() {
4176
+ super(...arguments);
4177
+ this.name = PROVIDER.GOOGLE.NAME;
4178
+ this.defaultModel = PROVIDER.GOOGLE.DEFAULT;
4179
+ // Session-level cache of Gemini 3 models observed to reject the native
4180
+ // `responseJsonSchema` + tools combo. When a model is in this set,
4181
+ // buildRequest engages the legacy fake-tool path instead.
4182
+ this.runtimeNoStructuredOutputComboModels = new Set();
4183
+ }
4184
+ rememberModelRejectsStructuredOutputCombo(model) {
4185
+ this.runtimeNoStructuredOutputComboModels.add(model);
4186
+ }
4187
+ clearRuntimeNoStructuredOutputComboModels() {
4188
+ this.runtimeNoStructuredOutputComboModels.clear();
4189
+ }
4190
+ supportsStructuredOutputCombo(model) {
4191
+ if (this.runtimeNoStructuredOutputComboModels.has(model))
4192
+ return false;
4193
+ return GEMINI_3_PATTERN.test(model);
4194
+ }
4195
+ //
4196
+ // Request Building
4197
+ //
4198
+ buildRequest(request) {
4199
+ // Convert messages to Gemini format (Content[])
4200
+ const contents = this.convertMessagesToContents(request.messages);
4201
+ const geminiRequest = {
4202
+ model: request.model || this.defaultModel,
4203
+ contents,
4204
+ };
4205
+ // Add system instruction if provided
4206
+ if (request.system) {
4207
+ geminiRequest.config = {
4208
+ ...geminiRequest.config,
4209
+ systemInstruction: request.system,
4210
+ };
4211
+ }
4212
+ // Append instructions to the last user message if provided
4213
+ if (request.instructions && contents.length > 0) {
4214
+ const lastContent = contents[contents.length - 1];
4215
+ if (lastContent.role === "user" && lastContent.parts) {
4216
+ const lastPart = lastContent.parts[lastContent.parts.length - 1];
4217
+ if (lastPart.text) {
4218
+ lastPart.text = lastPart.text + "\n\n" + request.instructions;
4219
+ }
4220
+ }
4221
+ }
4222
+ const hasUserTools = !!(request.tools && request.tools.length > 0);
4223
+ const useNativeCombo = Boolean(request.format) &&
4224
+ hasUserTools &&
4225
+ this.supportsStructuredOutputCombo(geminiRequest.model);
4226
+ // When tools+format are combined and the model does not support the native
4227
+ // combo, inject the legacy `structured_output` fake tool here so the model
4228
+ // is forced to call it before its final answer.
4229
+ const allTools = request.tools
4230
+ ? [...request.tools]
4231
+ : [];
4232
+ if (request.format && hasUserTools && !useNativeCombo) {
4233
+ log$1.log.warn(`[GoogleAdapter] Using legacy structured_output tool fallback for model ${geminiRequest.model}; native responseJsonSchema + tools combo is only available on Gemini 3.`);
4234
+ allTools.push({
4235
+ name: STRUCTURED_OUTPUT_TOOL_NAME$1,
4236
+ description: "Output a structured JSON object, " +
4237
+ "use this before your final response to give structured outputs to the user",
3332
4238
  parameters: request.format,
3333
4239
  });
3334
4240
  }
@@ -6038,8 +6944,8 @@ async function withLlmObsSpan(options, fn) {
6038
6944
  if (started) {
6039
6945
  throw error;
6040
6946
  }
6041
- getLogger$6().warn("[llmobs] span emission failed");
6042
- getLogger$6().var({ error });
6947
+ getLogger$7().warn("[llmobs] span emission failed");
6948
+ getLogger$7().var({ error });
6043
6949
  return fn();
6044
6950
  }
6045
6951
  }
@@ -6055,8 +6961,8 @@ function annotateLlmObs(annotation) {
6055
6961
  sdk.annotate(undefined, annotation);
6056
6962
  }
6057
6963
  catch (error) {
6058
- getLogger$6().warn("[llmobs] annotate failed");
6059
- getLogger$6().var({ error });
6964
+ getLogger$7().warn("[llmobs] annotate failed");
6965
+ getLogger$7().var({ error });
6060
6966
  }
6061
6967
  }
6062
6968
  /**
@@ -6093,8 +6999,8 @@ function openLlmObsSpan(options) {
6093
6999
  sdk.annotate(capturedSpan, annotation);
6094
7000
  }
6095
7001
  catch (error) {
6096
- getLogger$6().warn("[llmobs] annotate failed");
6097
- getLogger$6().var({ error });
7002
+ getLogger$7().warn("[llmobs] annotate failed");
7003
+ getLogger$7().var({ error });
6098
7004
  }
6099
7005
  },
6100
7006
  finish: () => {
@@ -6107,8 +7013,8 @@ function openLlmObsSpan(options) {
6107
7013
  };
6108
7014
  }
6109
7015
  catch (error) {
6110
- getLogger$6().warn("[llmobs] span emission failed");
6111
- getLogger$6().var({ error });
7016
+ getLogger$7().warn("[llmobs] span emission failed");
7017
+ getLogger$7().var({ error });
6112
7018
  return null;
6113
7019
  }
6114
7020
  }
@@ -6586,7 +7492,7 @@ async function emitProgress({ event, onProgress, }) {
6586
7492
  await onProgress(event);
6587
7493
  }
6588
7494
  catch (error) {
6589
- const log = getLogger$6();
7495
+ const log = getLogger$7();
6590
7496
  log.warn(`[operate] onProgress callback threw on "${event.type}" event`);
6591
7497
  log.var({ error });
6592
7498
  }
@@ -6836,7 +7742,7 @@ function matchesCaughtError(reason, caught) {
6836
7742
  * (e.g. undici `TypeError: terminated`) emitted between attempts.
6837
7743
  */
6838
7744
  function createStaleRejectionGuard() {
6839
- const log = getLogger$6();
7745
+ const log = getLogger$7();
6840
7746
  const caughtErrors = new Set();
6841
7747
  let listener;
6842
7748
  return {
@@ -7023,7 +7929,7 @@ class RetryExecutor {
7023
7929
  * @throws BadGatewayError if all retries are exhausted or error is not retryable
7024
7930
  */
7025
7931
  async execute(operation, options) {
7026
- const log = getLogger$6();
7932
+ const log = getLogger$7();
7027
7933
  let attempt = 0;
7028
7934
  // Guard against stale rejections firing on a subsequent microtask after
7029
7935
  // the retry layer has already caught the originating error: undici socket
@@ -7117,6 +8023,31 @@ function createErrorClassifier(adapter) {
7117
8023
  },
7118
8024
  };
7119
8025
  }
8026
+ /**
8027
+ * Attempt to read a prose response as the structured payload itself: parse the
8028
+ * text (stripping a Markdown code fence if present) and return the object, or
8029
+ * undefined when the text is not a JSON object.
8030
+ */
8031
+ function tryParseJsonObject(text) {
8032
+ let candidate = text.trim();
8033
+ const fence = candidate.match(/^```(?:json)?\s*([\s\S]*?)\s*```$/);
8034
+ if (fence) {
8035
+ candidate = fence[1].trim();
8036
+ }
8037
+ if (!candidate.startsWith("{")) {
8038
+ return undefined;
8039
+ }
8040
+ try {
8041
+ const parsed = JSON.parse(candidate);
8042
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
8043
+ return parsed;
8044
+ }
8045
+ }
8046
+ catch {
8047
+ // Not JSON; caller falls through to the corrective-turn path.
8048
+ }
8049
+ return undefined;
8050
+ }
7120
8051
  //
7121
8052
  //
7122
8053
  // Main
@@ -7141,7 +8072,7 @@ class OperateLoop {
7141
8072
  * Execute the operate loop for multi-turn conversations with tool calling.
7142
8073
  */
7143
8074
  async execute(input, options = {}) {
7144
- const log = getLogger$6();
8075
+ const log = getLogger$7();
7145
8076
  // Log what was passed to operate
7146
8077
  log.trace("[operate] Starting operate loop");
7147
8078
  log.trace.var({ "operate.input": input });
@@ -7185,6 +8116,7 @@ class OperateLoop {
7185
8116
  messages: state.currentInput,
7186
8117
  model: modelName,
7187
8118
  providerOptions: options.providerOptions,
8119
+ structuredOutputRetry: state.structuredOutputRetry,
7188
8120
  system: options.system,
7189
8121
  temperature: options.temperature,
7190
8122
  tools: state.formattedTools,
@@ -7296,7 +8228,7 @@ class OperateLoop {
7296
8228
  };
7297
8229
  }
7298
8230
  async executeOneTurn(request, state, context, options) {
7299
- const log = getLogger$6();
8231
+ const log = getLogger$7();
7300
8232
  // Create error classifier from adapter
7301
8233
  const errorClassifier = createErrorClassifier(this.adapter);
7302
8234
  // Create retry executor for this turn
@@ -7588,6 +8520,42 @@ class OperateLoop {
7588
8520
  return true; // Continue to next turn
7589
8521
  }
7590
8522
  }
8523
+ // Format contract enforcement: the loop is about to complete but the
8524
+ // model answered with prose instead of structured output.
8525
+ if (state.formattedFormat && typeof parsed.content === "string") {
8526
+ // First salvage attempt: the text may be the JSON itself (with or
8527
+ // without a code fence).
8528
+ const salvaged = tryParseJsonObject(parsed.content);
8529
+ if (salvaged) {
8530
+ state.responseBuilder.setContent(this.applyFormatArrayDefaults(salvaged, options));
8531
+ state.responseBuilder.complete();
8532
+ for (const item of this.adapter.responseToHistoryItems(parsed.raw)) {
8533
+ state.responseBuilder.appendToHistory(item);
8534
+ }
8535
+ return false; // Stop loop
8536
+ }
8537
+ // Corrective turn: for adapters whose structured output rides a tool
8538
+ // emulation, take another turn offering only the structured_output tool
8539
+ // and demand it be called. Bounded by maxTurns.
8540
+ if (this.adapter.supportsStructuredOutputRetry &&
8541
+ state.currentTurn < state.maxTurns) {
8542
+ log.warn(`[operate] Model returned text despite format on turn ${state.currentTurn}; retrying with structured_output tool only`);
8543
+ for (const item of this.adapter.responseToHistoryItems(parsed.raw)) {
8544
+ state.currentInput.push(item);
8545
+ state.responseBuilder.appendToHistory(item);
8546
+ }
8547
+ const corrective = {
8548
+ content: "You must provide your final answer by calling the structured_output tool " +
8549
+ "with arguments matching the required schema. Do not respond with text.",
8550
+ role: exports.LlmMessageRole.User,
8551
+ type: exports.LlmMessageType.Message,
8552
+ };
8553
+ state.currentInput.push(corrective);
8554
+ state.responseBuilder.appendToHistory(corrective);
8555
+ state.structuredOutputRetry = true;
8556
+ return true; // Continue to corrective turn
8557
+ }
8558
+ }
7591
8559
  // No tool calls or no toolkit - we're done
7592
8560
  state.responseBuilder.setContent(this.applyFormatArrayDefaults(parsed.content, options));
7593
8561
  state.responseBuilder.complete();
@@ -7762,7 +8730,7 @@ class StreamLoop {
7762
8730
  * Yields stream chunks as they become available.
7763
8731
  */
7764
8732
  async *execute(input, options = {}) {
7765
- const log = getLogger$6();
8733
+ const log = getLogger$7();
7766
8734
  // Verify adapter supports streaming
7767
8735
  if (!this.adapter.executeStreamRequest) {
7768
8736
  throw new errors.BadGatewayError(`Provider ${this.adapter.name} does not support streaming`);
@@ -7890,7 +8858,7 @@ class StreamLoop {
7890
8858
  };
7891
8859
  }
7892
8860
  async *executeOneStreamingTurn(request, state, context, options) {
7893
- const log = getLogger$6();
8861
+ const log = getLogger$7();
7894
8862
  // Build provider-specific request
7895
8863
  const providerRequest = this.adapter.buildRequest(request);
7896
8864
  // Execute beforeEachModelRequest hook
@@ -8047,7 +9015,7 @@ class StreamLoop {
8047
9015
  return { shouldContinue: false };
8048
9016
  }
8049
9017
  async *processToolCalls(toolCalls, state, context) {
8050
- const log = getLogger$6();
9018
+ const log = getLogger$7();
8051
9019
  for (const toolCall of toolCalls) {
8052
9020
  state.toolCallNames.push(toolCall.name);
8053
9021
  // Resolved once per call; never throws (undefined when tool has no message)
@@ -8330,10 +9298,10 @@ class AnthropicClient {
8330
9298
  }
8331
9299
 
8332
9300
  // Logger
8333
- const getLogger$5 = () => log$1.log.lib({ lib: kit.JAYPIE.LIB.LLM });
9301
+ const getLogger$6 = () => log$1.log.lib({ lib: kit.JAYPIE.LIB.LLM });
8334
9302
  // Client initialization
8335
- async function initializeClient$5({ apiKey, } = {}) {
8336
- const logger = getLogger$5();
9303
+ async function initializeClient$6({ apiKey, } = {}) {
9304
+ const logger = getLogger$6();
8337
9305
  const resolvedApiKey = apiKey || (await aws.getEnvSecret("ANTHROPIC_API_KEY"));
8338
9306
  if (!resolvedApiKey) {
8339
9307
  throw new errors.ConfigurationError("The application could not resolve the required API key: ANTHROPIC_API_KEY");
@@ -8343,12 +9311,12 @@ async function initializeClient$5({ apiKey, } = {}) {
8343
9311
  return client;
8344
9312
  }
8345
9313
  // Message formatting functions
8346
- function formatSystemMessage$2(systemPrompt, { data, placeholders } = {}) {
9314
+ function formatSystemMessage$3(systemPrompt, { data, placeholders } = {}) {
8347
9315
  return placeholders?.system === false
8348
9316
  ? systemPrompt
8349
9317
  : kit.placeholders(systemPrompt, data);
8350
9318
  }
8351
- function formatUserMessage$3(message, { data, placeholders } = {}) {
9319
+ function formatUserMessage$4(message, { data, placeholders } = {}) {
8352
9320
  const content = placeholders?.message === false
8353
9321
  ? message
8354
9322
  : kit.placeholders(message, data);
@@ -8357,11 +9325,11 @@ function formatUserMessage$3(message, { data, placeholders } = {}) {
8357
9325
  content,
8358
9326
  };
8359
9327
  }
8360
- function prepareMessages$3(message, { data, placeholders } = {}) {
8361
- const logger = getLogger$5();
9328
+ function prepareMessages$4(message, { data, placeholders } = {}) {
9329
+ const logger = getLogger$6();
8362
9330
  const messages = [];
8363
9331
  // Add user message (necessary for all requests)
8364
- const userMessage = formatUserMessage$3(message, { data, placeholders });
9332
+ const userMessage = formatUserMessage$4(message, { data, placeholders });
8365
9333
  messages.push(userMessage);
8366
9334
  logger.trace(`User message: ${userMessage.content.length} characters`);
8367
9335
  return messages;
@@ -8443,7 +9411,7 @@ async function createStructuredCompletion$1(client, messages, model, responseSch
8443
9411
  // Main class implementation
8444
9412
  class AnthropicProvider {
8445
9413
  constructor(model = PROVIDER.ANTHROPIC.DEFAULT, { apiKey } = {}) {
8446
- this.log = getLogger$5();
9414
+ this.log = getLogger$6();
8447
9415
  this.conversationHistory = [];
8448
9416
  this.model = model;
8449
9417
  this.apiKey = apiKey;
@@ -8452,7 +9420,7 @@ class AnthropicProvider {
8452
9420
  if (this._client) {
8453
9421
  return this._client;
8454
9422
  }
8455
- this._client = await initializeClient$5({ apiKey: this.apiKey });
9423
+ this._client = await initializeClient$6({ apiKey: this.apiKey });
8456
9424
  return this._client;
8457
9425
  }
8458
9426
  async getOperateLoop() {
@@ -8480,12 +9448,12 @@ class AnthropicProvider {
8480
9448
  // Main send method
8481
9449
  async send(message, options) {
8482
9450
  const client = await this.getClient();
8483
- const messages = prepareMessages$3(message, options || {});
9451
+ const messages = prepareMessages$4(message, options || {});
8484
9452
  const modelToUse = options?.model || this.model;
8485
9453
  // Process system message if provided
8486
9454
  let systemMessage;
8487
9455
  if (options?.system) {
8488
- systemMessage = formatSystemMessage$2(options.system, {
9456
+ systemMessage = formatSystemMessage$3(options.system, {
8489
9457
  data: options.data,
8490
9458
  placeholders: options.placeholders,
8491
9459
  });
@@ -8539,9 +9507,9 @@ async function loadSdk() {
8539
9507
  throw new errors.ConfigurationError("@aws-sdk/client-bedrock-runtime is required but not installed. Run: npm install @aws-sdk/client-bedrock-runtime");
8540
9508
  }
8541
9509
  }
8542
- const getLogger$4 = () => log$1.log.lib({ lib: kit.JAYPIE.LIB.LLM });
8543
- async function initializeClient$4({ region, } = {}) {
8544
- const logger = getLogger$4();
9510
+ const getLogger$5 = () => log$1.log.lib({ lib: kit.JAYPIE.LIB.LLM });
9511
+ async function initializeClient$5({ region, } = {}) {
9512
+ const logger = getLogger$5();
8545
9513
  const resolvedRegion = region || process.env.AWS_REGION || "us-east-1";
8546
9514
  const sdk = await loadSdk();
8547
9515
  const client = new sdk.BedrockRuntimeClient({ region: resolvedRegion });
@@ -8551,7 +9519,7 @@ async function initializeClient$4({ region, } = {}) {
8551
9519
 
8552
9520
  class BedrockProvider {
8553
9521
  constructor(model = PROVIDER.BEDROCK.DEFAULT, { region } = {}) {
8554
- this.log = getLogger$4();
9522
+ this.log = getLogger$5();
8555
9523
  this.conversationHistory = [];
8556
9524
  this.model = model;
8557
9525
  this.region = region;
@@ -8559,7 +9527,7 @@ class BedrockProvider {
8559
9527
  async getClient() {
8560
9528
  if (this._client)
8561
9529
  return this._client;
8562
- this._client = await initializeClient$4({ region: this.region });
9530
+ this._client = await initializeClient$5({ region: this.region });
8563
9531
  return this._client;
8564
9532
  }
8565
9533
  async getOperateLoop() {
@@ -8608,6 +9576,278 @@ class BedrockProvider {
8608
9576
  }
8609
9577
  }
8610
9578
 
9579
+ /**
9580
+ * HTTP error carrying the upstream status and parsed API message. The
9581
+ * FireworksAdapter classifies errors by reading `.status` / `.statusCode` and
9582
+ * `.message` / `.error.message`.
9583
+ */
9584
+ class FireworksHttpError extends Error {
9585
+ constructor(status, message, error) {
9586
+ super(message);
9587
+ this.name = "FireworksHttpError";
9588
+ this.status = status;
9589
+ this.statusCode = status;
9590
+ this.error = error;
9591
+ }
9592
+ }
9593
+ //
9594
+ //
9595
+ // Helpers
9596
+ //
9597
+ /**
9598
+ * Normalize the snake_case wire response into the camelCase shape the adapter
9599
+ * readers expect. Only protocol fields are touched — user content (schema
9600
+ * property names, tool argument JSON) is left untouched.
9601
+ */
9602
+ function normalizeResponse$1(json) {
9603
+ const choices = json.choices;
9604
+ if (Array.isArray(choices)) {
9605
+ for (const choice of choices) {
9606
+ if (choice.finish_reason !== undefined &&
9607
+ choice.finishReason === undefined) {
9608
+ choice.finishReason = choice.finish_reason;
9609
+ }
9610
+ const message = choice.message;
9611
+ if (message?.tool_calls !== undefined &&
9612
+ message.toolCalls === undefined) {
9613
+ message.toolCalls = message.tool_calls;
9614
+ }
9615
+ }
9616
+ }
9617
+ const usage = json.usage;
9618
+ if (usage) {
9619
+ if (usage.promptTokens === undefined)
9620
+ usage.promptTokens = usage.prompt_tokens;
9621
+ if (usage.completionTokens === undefined) {
9622
+ usage.completionTokens = usage.completion_tokens;
9623
+ }
9624
+ if (usage.totalTokens === undefined)
9625
+ usage.totalTokens = usage.total_tokens;
9626
+ const details = usage.completion_tokens_details;
9627
+ if (details?.reasoning_tokens !== undefined &&
9628
+ !usage.completionTokensDetails) {
9629
+ usage.completionTokensDetails = {
9630
+ reasoningTokens: details.reasoning_tokens,
9631
+ };
9632
+ }
9633
+ }
9634
+ return json;
9635
+ }
9636
+ //
9637
+ //
9638
+ // Main
9639
+ //
9640
+ /**
9641
+ * Minimal `fetch`-based client for Fireworks AI's OpenAI-compatible Chat
9642
+ * Completions endpoint. The adapter only needs a single POST (streaming and
9643
+ * non-streaming), header auth, and HTTP error surfacing.
9644
+ */
9645
+ class FireworksClient {
9646
+ constructor({ apiKey, baseURL = PROVIDER.FIREWORKS.BASE_URL, }) {
9647
+ this.apiKey = apiKey;
9648
+ this.baseURL = baseURL;
9649
+ }
9650
+ headers() {
9651
+ return {
9652
+ Authorization: `Bearer ${this.apiKey}`,
9653
+ "Content-Type": "application/json",
9654
+ };
9655
+ }
9656
+ async toError(response) {
9657
+ let message = `Fireworks request failed with status ${response.status}`;
9658
+ let error;
9659
+ try {
9660
+ const body = (await response.json());
9661
+ if (body?.error?.message) {
9662
+ message = body.error.message;
9663
+ error = body.error;
9664
+ }
9665
+ }
9666
+ catch {
9667
+ // Non-JSON error body; keep the status-based message.
9668
+ }
9669
+ return new FireworksHttpError(response.status, message, error);
9670
+ }
9671
+ async chatCompletion(body, { signal } = {}) {
9672
+ const response = await fetch(`${this.baseURL}/chat/completions`, {
9673
+ method: "POST",
9674
+ headers: this.headers(),
9675
+ body: JSON.stringify(body),
9676
+ signal,
9677
+ });
9678
+ if (!response.ok)
9679
+ throw await this.toError(response);
9680
+ const json = (await response.json());
9681
+ return normalizeResponse$1(json);
9682
+ }
9683
+ async *streamChatCompletion(body, { signal } = {}) {
9684
+ const response = await fetch(`${this.baseURL}/chat/completions`, {
9685
+ method: "POST",
9686
+ headers: { ...this.headers(), Accept: "text/event-stream" },
9687
+ // OpenAI-style streams only include usage when explicitly requested.
9688
+ body: JSON.stringify({
9689
+ ...body,
9690
+ stream: true,
9691
+ stream_options: { include_usage: true },
9692
+ }),
9693
+ signal,
9694
+ });
9695
+ if (!response.ok)
9696
+ throw await this.toError(response);
9697
+ if (!response.body)
9698
+ return;
9699
+ yield* parseSseStream(response.body);
9700
+ }
9701
+ }
9702
+
9703
+ // Logger
9704
+ const getLogger$4 = () => log$1.log.lib({ lib: kit.JAYPIE.LIB.LLM });
9705
+ // Client initialization
9706
+ async function initializeClient$4({ apiKey, } = {}) {
9707
+ const logger = getLogger$4();
9708
+ const resolvedApiKey = apiKey || (await aws.getEnvSecret(PROVIDER.FIREWORKS.API_KEY));
9709
+ if (!resolvedApiKey) {
9710
+ throw new errors.ConfigurationError("The application could not resolve the requested keys");
9711
+ }
9712
+ const client = new FireworksClient({ apiKey: resolvedApiKey });
9713
+ logger.trace("Initialized Fireworks client");
9714
+ return client;
9715
+ }
9716
+ // Get default model from environment or constants
9717
+ function getDefaultModel$1() {
9718
+ return process.env.FIREWORKS_MODEL || PROVIDER.FIREWORKS.DEFAULT;
9719
+ }
9720
+ function formatSystemMessage$2(systemPrompt, { data, placeholders } = {}) {
9721
+ const content = placeholders?.system === false
9722
+ ? systemPrompt
9723
+ : kit.placeholders(systemPrompt, data);
9724
+ return {
9725
+ role: "system",
9726
+ content,
9727
+ };
9728
+ }
9729
+ function formatUserMessage$3(message, { data, placeholders } = {}) {
9730
+ const content = placeholders?.message === false
9731
+ ? message
9732
+ : kit.placeholders(message, data);
9733
+ return {
9734
+ role: "user",
9735
+ content,
9736
+ };
9737
+ }
9738
+ function prepareMessages$3(message, { system, data, placeholders } = {}) {
9739
+ const logger = getLogger$4();
9740
+ const messages = [];
9741
+ if (system) {
9742
+ const systemMessage = formatSystemMessage$2(system, { data, placeholders });
9743
+ messages.push(systemMessage);
9744
+ logger.trace(`System message: ${systemMessage.content?.length} characters`);
9745
+ }
9746
+ const userMessage = formatUserMessage$3(message, { data, placeholders });
9747
+ messages.push(userMessage);
9748
+ logger.trace(`User message: ${userMessage.content?.length} characters`);
9749
+ return messages;
9750
+ }
9751
+
9752
+ class FireworksProvider {
9753
+ constructor(model = getDefaultModel$1(), { apiKey } = {}) {
9754
+ this.log = getLogger$4();
9755
+ this.conversationHistory = [];
9756
+ this.model = model;
9757
+ this.apiKey = apiKey;
9758
+ }
9759
+ async getClient() {
9760
+ if (this._client) {
9761
+ return this._client;
9762
+ }
9763
+ this._client = await initializeClient$4({ apiKey: this.apiKey });
9764
+ return this._client;
9765
+ }
9766
+ async getOperateLoop() {
9767
+ if (this._operateLoop) {
9768
+ return this._operateLoop;
9769
+ }
9770
+ const client = await this.getClient();
9771
+ this._operateLoop = createOperateLoop({
9772
+ adapter: fireworksAdapter,
9773
+ client,
9774
+ });
9775
+ return this._operateLoop;
9776
+ }
9777
+ async getStreamLoop() {
9778
+ if (this._streamLoop) {
9779
+ return this._streamLoop;
9780
+ }
9781
+ const client = await this.getClient();
9782
+ this._streamLoop = createStreamLoop({
9783
+ adapter: fireworksAdapter,
9784
+ client,
9785
+ });
9786
+ return this._streamLoop;
9787
+ }
9788
+ async send(message, options) {
9789
+ const client = await this.getClient();
9790
+ const messages = prepareMessages$3(message, options);
9791
+ const modelToUse = options?.model || this.model;
9792
+ // OpenAI-compatible Chat Completions body; messages are already wire-shaped.
9793
+ const response = await client.chatCompletion({
9794
+ model: modelToUse,
9795
+ messages,
9796
+ });
9797
+ const choices = response.choices;
9798
+ const rawContent = choices?.[0]?.message?.content;
9799
+ // Extract text content - content could be string or array of content items
9800
+ const content = typeof rawContent === "string"
9801
+ ? rawContent
9802
+ : Array.isArray(rawContent)
9803
+ ? rawContent
9804
+ .filter((item) => item.type === "text")
9805
+ .map((item) => item.text)
9806
+ .join("")
9807
+ : "";
9808
+ this.log.trace(`Assistant reply: ${content?.length || 0} characters`);
9809
+ // If structured output was requested, try to parse the response
9810
+ if (options?.response && content) {
9811
+ try {
9812
+ return JSON.parse(content);
9813
+ }
9814
+ catch {
9815
+ return content || "";
9816
+ }
9817
+ }
9818
+ return content || "";
9819
+ }
9820
+ async operate(input, options = {}) {
9821
+ const operateLoop = await this.getOperateLoop();
9822
+ const mergedOptions = { ...options, model: options.model ?? this.model };
9823
+ // Create a merged history including both the tracked history and any explicitly provided history
9824
+ if (this.conversationHistory.length > 0) {
9825
+ mergedOptions.history = options.history
9826
+ ? [...this.conversationHistory, ...options.history]
9827
+ : [...this.conversationHistory];
9828
+ }
9829
+ // Execute operate loop
9830
+ const response = await operateLoop.execute(input, mergedOptions);
9831
+ // Update conversation history with the new history from the response
9832
+ if (response.history && response.history.length > 0) {
9833
+ this.conversationHistory = response.history;
9834
+ }
9835
+ return response;
9836
+ }
9837
+ async *stream(input, options = {}) {
9838
+ const streamLoop = await this.getStreamLoop();
9839
+ const mergedOptions = { ...options, model: options.model ?? this.model };
9840
+ // Create a merged history including both the tracked history and any explicitly provided history
9841
+ if (this.conversationHistory.length > 0) {
9842
+ mergedOptions.history = options.history
9843
+ ? [...this.conversationHistory, ...options.history]
9844
+ : [...this.conversationHistory];
9845
+ }
9846
+ // Execute stream loop
9847
+ yield* streamLoop.execute(input, mergedOptions);
9848
+ }
9849
+ }
9850
+
8611
9851
  //
8612
9852
  //
8613
9853
  // Constants
@@ -9521,6 +10761,10 @@ class Llm {
9521
10761
  });
9522
10762
  case PROVIDER.BEDROCK.NAME:
9523
10763
  return new BedrockProvider(model || PROVIDER.BEDROCK.DEFAULT);
10764
+ case PROVIDER.FIREWORKS.NAME:
10765
+ return new FireworksProvider(model || PROVIDER.FIREWORKS.DEFAULT, {
10766
+ apiKey,
10767
+ });
9524
10768
  case PROVIDER.GOOGLE.NAME:
9525
10769
  return new GoogleProvider(model || PROVIDER.GOOGLE.DEFAULT, {
9526
10770
  apiKey,
@@ -9816,7 +11060,7 @@ const roll = {
9816
11060
  },
9817
11061
  type: "function",
9818
11062
  call: ({ number = 1, sides = 6 } = {}) => {
9819
- const log = getLogger$6();
11063
+ const log = getLogger$7();
9820
11064
  const rng = random$1();
9821
11065
  const rolls = [];
9822
11066
  let total = 0;
@@ -10015,6 +11259,7 @@ const toolkit = new JaypieToolkit(tools);
10015
11259
  });
10016
11260
 
10017
11261
  exports.BedrockProvider = BedrockProvider;
11262
+ exports.FireworksProvider = FireworksProvider;
10018
11263
  exports.GeminiProvider = GoogleProvider;
10019
11264
  exports.GoogleProvider = GoogleProvider;
10020
11265
  exports.JaypieToolkit = JaypieToolkit;