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