@jaypie/llm 1.2.31 → 1.2.33
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/index.cjs +155 -23
- package/dist/cjs/index.cjs.map +1 -1
- package/dist/cjs/operate/adapters/OpenAiAdapter.d.ts +4 -0
- package/dist/cjs/operate/adapters/OpenRouterAdapter.d.ts +20 -1
- package/dist/esm/index.js +155 -23
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/operate/adapters/OpenAiAdapter.d.ts +4 -0
- package/dist/esm/operate/adapters/OpenRouterAdapter.d.ts +20 -1
- package/package.json +1 -1
package/dist/cjs/index.cjs
CHANGED
|
@@ -1199,11 +1199,11 @@ const NOT_RETRYABLE_ERROR_NAMES = [
|
|
|
1199
1199
|
// Patterns (not exact names) so dated variants and future releases are covered
|
|
1200
1200
|
// without code changes — Anthropic is trending toward removing temperature on
|
|
1201
1201
|
// newer Claude models.
|
|
1202
|
-
const MODELS_WITHOUT_TEMPERATURE = [
|
|
1202
|
+
const MODELS_WITHOUT_TEMPERATURE$2 = [
|
|
1203
1203
|
/^claude-opus-4-[789]/,
|
|
1204
1204
|
/^claude-opus-[5-9]/,
|
|
1205
1205
|
];
|
|
1206
|
-
function isTemperatureDeprecationError(error) {
|
|
1206
|
+
function isTemperatureDeprecationError$2(error) {
|
|
1207
1207
|
if (!error || typeof error !== "object")
|
|
1208
1208
|
return false;
|
|
1209
1209
|
const err = error;
|
|
@@ -1273,7 +1273,7 @@ class AnthropicAdapter extends BaseProviderAdapter {
|
|
|
1273
1273
|
supportsTemperature(model) {
|
|
1274
1274
|
if (this.runtimeNoTemperatureModels.has(model))
|
|
1275
1275
|
return false;
|
|
1276
|
-
return !MODELS_WITHOUT_TEMPERATURE.some((pattern) => pattern.test(model));
|
|
1276
|
+
return !MODELS_WITHOUT_TEMPERATURE$2.some((pattern) => pattern.test(model));
|
|
1277
1277
|
}
|
|
1278
1278
|
supportsStructuredOutput(model) {
|
|
1279
1279
|
return !this.runtimeNoStructuredOutputModels.has(model);
|
|
@@ -1451,7 +1451,7 @@ class AnthropicAdapter extends BaseProviderAdapter {
|
|
|
1451
1451
|
return undefined;
|
|
1452
1452
|
// If the model rejected `temperature`, cache it and retry without the param
|
|
1453
1453
|
if (anthropicRequest.temperature !== undefined &&
|
|
1454
|
-
isTemperatureDeprecationError(error)) {
|
|
1454
|
+
isTemperatureDeprecationError$2(error)) {
|
|
1455
1455
|
this.rememberModelRejectsTemperature(anthropicRequest.model);
|
|
1456
1456
|
const retryRequest = { ...anthropicRequest };
|
|
1457
1457
|
delete retryRequest.temperature;
|
|
@@ -1512,7 +1512,7 @@ class AnthropicAdapter extends BaseProviderAdapter {
|
|
|
1512
1512
|
}
|
|
1513
1513
|
catch (error) {
|
|
1514
1514
|
if (streamRequest.temperature !== undefined &&
|
|
1515
|
-
isTemperatureDeprecationError(error)) {
|
|
1515
|
+
isTemperatureDeprecationError$2(error)) {
|
|
1516
1516
|
this.rememberModelRejectsTemperature(streamRequest.model);
|
|
1517
1517
|
streamRequest = {
|
|
1518
1518
|
...streamRequest,
|
|
@@ -2239,14 +2239,24 @@ class GeminiAdapter extends BaseProviderAdapter {
|
|
|
2239
2239
|
// Tool Result Handling
|
|
2240
2240
|
//
|
|
2241
2241
|
formatToolResult(toolCall, result) {
|
|
2242
|
-
// Gemini
|
|
2243
|
-
//
|
|
2242
|
+
// Gemini's `function_response.response` is a protobuf Struct, which only
|
|
2243
|
+
// accepts plain objects. Some models (e.g. gemini-3.1-pro) accept scalar
|
|
2244
|
+
// values silently; gemini-3.1-flash-lite rejects them. Parse the output
|
|
2245
|
+
// and wrap any non-object value (string, number, boolean, array, null)
|
|
2246
|
+
// in `{ result: value }` so every tool result is a valid Struct.
|
|
2244
2247
|
let responseData;
|
|
2245
2248
|
try {
|
|
2246
|
-
|
|
2249
|
+
const parsed = JSON.parse(result.output);
|
|
2250
|
+
if (parsed !== null &&
|
|
2251
|
+
typeof parsed === "object" &&
|
|
2252
|
+
!Array.isArray(parsed)) {
|
|
2253
|
+
responseData = parsed;
|
|
2254
|
+
}
|
|
2255
|
+
else {
|
|
2256
|
+
responseData = { result: parsed };
|
|
2257
|
+
}
|
|
2247
2258
|
}
|
|
2248
2259
|
catch {
|
|
2249
|
-
// If parsing fails, wrap the output as a string result
|
|
2250
2260
|
responseData = { result: result.output };
|
|
2251
2261
|
}
|
|
2252
2262
|
return {
|
|
@@ -2663,6 +2673,27 @@ const NOT_RETRYABLE_ERROR_TYPES = [
|
|
|
2663
2673
|
openai.RateLimitError,
|
|
2664
2674
|
openai.UnprocessableEntityError,
|
|
2665
2675
|
];
|
|
2676
|
+
// Models known not to accept `temperature`.
|
|
2677
|
+
// Patterns (not exact names) so dated variants and future releases are covered
|
|
2678
|
+
// without code changes — OpenAI is removing temperature on newer reasoning
|
|
2679
|
+
// models.
|
|
2680
|
+
const MODELS_WITHOUT_TEMPERATURE$1 = [
|
|
2681
|
+
/^gpt-5\.5/, // gpt-5.5 series deprecated temperature
|
|
2682
|
+
/^o\d/, // o-series reasoning models (o1, o3, o4, ...)
|
|
2683
|
+
];
|
|
2684
|
+
function isTemperatureDeprecationError$1(error) {
|
|
2685
|
+
if (!error || typeof error !== "object")
|
|
2686
|
+
return false;
|
|
2687
|
+
if (!(error instanceof openai.BadRequestError) &&
|
|
2688
|
+
error.status !== 400) {
|
|
2689
|
+
return false;
|
|
2690
|
+
}
|
|
2691
|
+
const messages = [
|
|
2692
|
+
error.message,
|
|
2693
|
+
error.error?.message,
|
|
2694
|
+
].filter((m) => typeof m === "string");
|
|
2695
|
+
return messages.some((m) => m.toLowerCase().includes("temperature"));
|
|
2696
|
+
}
|
|
2666
2697
|
//
|
|
2667
2698
|
//
|
|
2668
2699
|
// Main
|
|
@@ -2677,6 +2708,20 @@ class OpenAiAdapter extends BaseProviderAdapter {
|
|
|
2677
2708
|
super(...arguments);
|
|
2678
2709
|
this.name = PROVIDER.OPENAI.NAME;
|
|
2679
2710
|
this.defaultModel = PROVIDER.OPENAI.MODEL.DEFAULT;
|
|
2711
|
+
// Session-level cache of models observed to reject `temperature` at runtime.
|
|
2712
|
+
// Populated by executeRequest on 400 errors so repeat calls skip the param.
|
|
2713
|
+
this.runtimeNoTemperatureModels = new Set();
|
|
2714
|
+
}
|
|
2715
|
+
rememberModelRejectsTemperature(model) {
|
|
2716
|
+
this.runtimeNoTemperatureModels.add(model);
|
|
2717
|
+
}
|
|
2718
|
+
clearRuntimeNoTemperatureModels() {
|
|
2719
|
+
this.runtimeNoTemperatureModels.clear();
|
|
2720
|
+
}
|
|
2721
|
+
supportsTemperature(model) {
|
|
2722
|
+
if (this.runtimeNoTemperatureModels.has(model))
|
|
2723
|
+
return false;
|
|
2724
|
+
return !MODELS_WITHOUT_TEMPERATURE$1.some((pattern) => pattern.test(model));
|
|
2680
2725
|
}
|
|
2681
2726
|
//
|
|
2682
2727
|
// Request Building
|
|
@@ -2720,6 +2765,11 @@ class OpenAiAdapter extends BaseProviderAdapter {
|
|
|
2720
2765
|
if (request.temperature !== undefined) {
|
|
2721
2766
|
openaiRequest.temperature = request.temperature;
|
|
2722
2767
|
}
|
|
2768
|
+
// Strip temperature for models that don't support it (denylist + runtime cache)
|
|
2769
|
+
if (openaiRequest.temperature !== undefined &&
|
|
2770
|
+
!this.supportsTemperature(openaiRequest.model)) {
|
|
2771
|
+
delete openaiRequest.temperature;
|
|
2772
|
+
}
|
|
2723
2773
|
return openaiRequest;
|
|
2724
2774
|
}
|
|
2725
2775
|
formatTools(toolkit, _outputSchema) {
|
|
@@ -2769,14 +2819,21 @@ class OpenAiAdapter extends BaseProviderAdapter {
|
|
|
2769
2819
|
//
|
|
2770
2820
|
async executeRequest(client, request, signal) {
|
|
2771
2821
|
const openai = client;
|
|
2822
|
+
const openaiRequest = request;
|
|
2772
2823
|
try {
|
|
2773
|
-
return await openai.responses.create(
|
|
2774
|
-
// @ts-expect-error OpenAI SDK types don't match our request format exactly
|
|
2775
|
-
request, signal ? { signal } : undefined);
|
|
2824
|
+
return await openai.responses.create(openaiRequest, signal ? { signal } : undefined);
|
|
2776
2825
|
}
|
|
2777
2826
|
catch (error) {
|
|
2778
2827
|
if (signal?.aborted)
|
|
2779
2828
|
return undefined;
|
|
2829
|
+
// If the model rejected `temperature`, cache it and retry without the param
|
|
2830
|
+
if (openaiRequest.temperature !== undefined &&
|
|
2831
|
+
isTemperatureDeprecationError$1(error)) {
|
|
2832
|
+
this.rememberModelRejectsTemperature(openaiRequest.model);
|
|
2833
|
+
const retryRequest = { ...openaiRequest };
|
|
2834
|
+
delete retryRequest.temperature;
|
|
2835
|
+
return await openai.responses.create(retryRequest, signal ? { signal } : undefined);
|
|
2836
|
+
}
|
|
2780
2837
|
throw error;
|
|
2781
2838
|
}
|
|
2782
2839
|
}
|
|
@@ -3121,10 +3178,31 @@ function isStructuredOutputUnsupportedError(error) {
|
|
|
3121
3178
|
const messages = [err.message, err.error?.message].filter((m) => typeof m === "string");
|
|
3122
3179
|
return messages.some((m) => /response_format|json[_ ]schema|structured[_ ]output|require[_ ]parameters/i.test(m));
|
|
3123
3180
|
}
|
|
3181
|
+
// OpenRouter routes that don't accept `temperature`. Patterns match the
|
|
3182
|
+
// vendor-prefixed route id (e.g. `openai/gpt-5.5`) so dated variants are
|
|
3183
|
+
// covered without code changes.
|
|
3184
|
+
const MODELS_WITHOUT_TEMPERATURE = [
|
|
3185
|
+
/^openai\/gpt-5\.5/,
|
|
3186
|
+
/^openai\/o\d/,
|
|
3187
|
+
/^anthropic\/claude-opus-4-[789]/,
|
|
3188
|
+
/^anthropic\/claude-opus-[5-9]/,
|
|
3189
|
+
];
|
|
3190
|
+
function isTemperatureDeprecationError(error) {
|
|
3191
|
+
if (!error || typeof error !== "object")
|
|
3192
|
+
return false;
|
|
3193
|
+
const err = error;
|
|
3194
|
+
const status = err.status ?? err.statusCode;
|
|
3195
|
+
if (status !== 400)
|
|
3196
|
+
return false;
|
|
3197
|
+
const messages = [err.message, err.error?.message].filter((m) => typeof m === "string");
|
|
3198
|
+
return messages.some((m) => m.toLowerCase().includes("temperature"));
|
|
3199
|
+
}
|
|
3124
3200
|
/**
|
|
3125
|
-
* Convert standardized content items to OpenRouter format
|
|
3126
|
-
*
|
|
3127
|
-
*
|
|
3201
|
+
* Convert standardized content items to OpenRouter format. Images become
|
|
3202
|
+
* `image_url` parts and files become `file` parts; both pass through to
|
|
3203
|
+
* OpenRouter which routes to the selected backend. Backends that don't
|
|
3204
|
+
* support the modality 4xx — that's a model-capability mismatch, surfaced
|
|
3205
|
+
* by the call rather than silently dropped here.
|
|
3128
3206
|
*/
|
|
3129
3207
|
function convertContentToOpenRouter(content) {
|
|
3130
3208
|
if (typeof content === "string") {
|
|
@@ -3132,25 +3210,38 @@ function convertContentToOpenRouter(content) {
|
|
|
3132
3210
|
}
|
|
3133
3211
|
const parts = [];
|
|
3134
3212
|
for (const item of content) {
|
|
3135
|
-
// Text content - pass through
|
|
3136
3213
|
if (item.type === exports.LlmMessageType.InputText) {
|
|
3137
3214
|
parts.push({ type: "text", text: item.text });
|
|
3138
3215
|
continue;
|
|
3139
3216
|
}
|
|
3140
|
-
// Image content - warn and discard
|
|
3141
3217
|
if (item.type === exports.LlmMessageType.InputImage) {
|
|
3142
|
-
|
|
3218
|
+
const url = item.image_url ?? "";
|
|
3219
|
+
if (!url) {
|
|
3220
|
+
log$1.log.warn("OpenRouter image content missing image_url; image discarded");
|
|
3221
|
+
continue;
|
|
3222
|
+
}
|
|
3223
|
+
parts.push({ type: "image_url", imageUrl: { url } });
|
|
3143
3224
|
continue;
|
|
3144
3225
|
}
|
|
3145
|
-
// File/Document content - warn and discard
|
|
3146
3226
|
if (item.type === exports.LlmMessageType.InputFile) {
|
|
3147
|
-
|
|
3227
|
+
const fileData = typeof item.file_data === "string" ? item.file_data : "";
|
|
3228
|
+
if (!fileData) {
|
|
3229
|
+
log$1.log.warn({ filename: item.filename }, "OpenRouter file content missing file_data; file discarded");
|
|
3230
|
+
continue;
|
|
3231
|
+
}
|
|
3232
|
+
parts.push({
|
|
3233
|
+
type: "file",
|
|
3234
|
+
file: {
|
|
3235
|
+
filename: item.filename,
|
|
3236
|
+
fileData,
|
|
3237
|
+
},
|
|
3238
|
+
});
|
|
3148
3239
|
continue;
|
|
3149
3240
|
}
|
|
3150
3241
|
// Unknown type - warn and skip
|
|
3151
3242
|
log$1.log.warn({ item }, "Unknown content type for OpenRouter; discarded");
|
|
3152
3243
|
}
|
|
3153
|
-
// If no
|
|
3244
|
+
// If no parts remain, return empty string to avoid empty array
|
|
3154
3245
|
if (parts.length === 0) {
|
|
3155
3246
|
return "";
|
|
3156
3247
|
}
|
|
@@ -3204,6 +3295,9 @@ class OpenRouterAdapter extends BaseProviderAdapter {
|
|
|
3204
3295
|
// `response_format: json_schema`. When a model is in this set, buildRequest
|
|
3205
3296
|
// engages the legacy fake-tool path instead of native structured output.
|
|
3206
3297
|
this.runtimeNoStructuredOutputModels = new Set();
|
|
3298
|
+
// Session-level cache of routes observed to reject `temperature`. Populated
|
|
3299
|
+
// by executeRequest on 400 errors so repeat calls skip the param.
|
|
3300
|
+
this.runtimeNoTemperatureModels = new Set();
|
|
3207
3301
|
}
|
|
3208
3302
|
rememberModelRejectsStructuredOutput(model) {
|
|
3209
3303
|
this.runtimeNoStructuredOutputModels.add(model);
|
|
@@ -3214,6 +3308,17 @@ class OpenRouterAdapter extends BaseProviderAdapter {
|
|
|
3214
3308
|
supportsStructuredOutput(model) {
|
|
3215
3309
|
return !this.runtimeNoStructuredOutputModels.has(model);
|
|
3216
3310
|
}
|
|
3311
|
+
rememberModelRejectsTemperature(model) {
|
|
3312
|
+
this.runtimeNoTemperatureModels.add(model);
|
|
3313
|
+
}
|
|
3314
|
+
clearRuntimeNoTemperatureModels() {
|
|
3315
|
+
this.runtimeNoTemperatureModels.clear();
|
|
3316
|
+
}
|
|
3317
|
+
supportsTemperature(model) {
|
|
3318
|
+
if (this.runtimeNoTemperatureModels.has(model))
|
|
3319
|
+
return false;
|
|
3320
|
+
return !MODELS_WITHOUT_TEMPERATURE.some((pattern) => pattern.test(model));
|
|
3321
|
+
}
|
|
3217
3322
|
//
|
|
3218
3323
|
// Request Building
|
|
3219
3324
|
//
|
|
@@ -3283,6 +3388,12 @@ class OpenRouterAdapter extends BaseProviderAdapter {
|
|
|
3283
3388
|
openRouterRequest.temperature =
|
|
3284
3389
|
request.temperature;
|
|
3285
3390
|
}
|
|
3391
|
+
// Strip temperature for routes that don't support it (denylist + runtime cache)
|
|
3392
|
+
const requestRecord = openRouterRequest;
|
|
3393
|
+
if (requestRecord.temperature !== undefined &&
|
|
3394
|
+
!this.supportsTemperature(openRouterRequest.model)) {
|
|
3395
|
+
delete requestRecord.temperature;
|
|
3396
|
+
}
|
|
3286
3397
|
return openRouterRequest;
|
|
3287
3398
|
}
|
|
3288
3399
|
formatTools(toolkit,
|
|
@@ -3309,11 +3420,18 @@ class OpenRouterAdapter extends BaseProviderAdapter {
|
|
|
3309
3420
|
jsonSchema.type = "object"; // Normalize type
|
|
3310
3421
|
}
|
|
3311
3422
|
else {
|
|
3312
|
-
// Convert NaturalSchema to JSON schema through Zod
|
|
3423
|
+
// Convert NaturalSchema to JSON schema through Zod. Re-spread into a
|
|
3424
|
+
// plain object: Zod v4's z.toJSONSchema returns an object that carries
|
|
3425
|
+
// a non-configurable `~standard` Standard-Schema interop marker as a
|
|
3426
|
+
// non-enumerable own property. Anthropic's output_config.format
|
|
3427
|
+
// validation is strict and rejects unknown properties when OpenRouter
|
|
3428
|
+
// forwards the schema, so we drop the marker here. JSON.stringify
|
|
3429
|
+
// already skips it but the OpenRouter SDK enumerates own properties
|
|
3430
|
+
// during serialization.
|
|
3313
3431
|
const zodSchema = schema instanceof v4.z.ZodType
|
|
3314
3432
|
? schema
|
|
3315
3433
|
: naturalZodSchema(schema);
|
|
3316
|
-
jsonSchema = v4.z.toJSONSchema(zodSchema);
|
|
3434
|
+
jsonSchema = { ...v4.z.toJSONSchema(zodSchema) };
|
|
3317
3435
|
}
|
|
3318
3436
|
// Remove $schema property (can cause issues with some providers)
|
|
3319
3437
|
if (jsonSchema.$schema) {
|
|
@@ -3341,6 +3459,20 @@ class OpenRouterAdapter extends BaseProviderAdapter {
|
|
|
3341
3459
|
catch (error) {
|
|
3342
3460
|
if (signal?.aborted)
|
|
3343
3461
|
return undefined;
|
|
3462
|
+
// If the route rejected `temperature` (e.g., openai/gpt-5.5 forwarding),
|
|
3463
|
+
// cache it and retry without the param.
|
|
3464
|
+
const requestRecord = openRouterRequest;
|
|
3465
|
+
if (requestRecord.temperature !== undefined &&
|
|
3466
|
+
isTemperatureDeprecationError(error)) {
|
|
3467
|
+
this.rememberModelRejectsTemperature(openRouterRequest.model);
|
|
3468
|
+
const retryRequest = { ...openRouterRequest };
|
|
3469
|
+
delete retryRequest.temperature;
|
|
3470
|
+
const response = (await openRouter.chat.send(this.toSdkChatParams(retryRequest), signal ? { signal } : undefined));
|
|
3471
|
+
if (wantsStructuredOutput) {
|
|
3472
|
+
response.__jaypieStructuredOutput = true;
|
|
3473
|
+
}
|
|
3474
|
+
return response;
|
|
3475
|
+
}
|
|
3344
3476
|
// If the model rejected `response_format`, cache it and retry with the
|
|
3345
3477
|
// legacy fake-tool emulation path.
|
|
3346
3478
|
if (wantsStructuredOutput && isStructuredOutputUnsupportedError(error)) {
|