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