llm-strings 1.4.0 → 1.5.0

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/index.cjs CHANGED
@@ -144,44 +144,92 @@ function providerFromHostAlias(alias) {
144
144
  }
145
145
  return void 0;
146
146
  }
147
+ function canonicalHostName(host) {
148
+ return normalizeHostValue(host).toLowerCase().split(":")[0] ?? host;
149
+ }
150
+ function hostMatches(host, ...domains) {
151
+ return domains.some((domain) => {
152
+ const normalizedDomain = domain.toLowerCase();
153
+ return host === normalizedDomain || host.endsWith(`.${normalizedDomain}`);
154
+ });
155
+ }
156
+ function hostHasLabel(host, ...labels) {
157
+ const hostLabels = host.split(".");
158
+ return labels.some((label) => hostLabels.includes(label.toLowerCase()));
159
+ }
160
+ function hostHasLabelPrefix(host, ...prefixes) {
161
+ const hostLabels = host.split(".");
162
+ return prefixes.some(
163
+ (prefix) => hostLabels.some((label) => label.startsWith(prefix.toLowerCase()))
164
+ );
165
+ }
166
+ var MODEL_FAMILY_DELIMITERS = /* @__PURE__ */ new Set(["-", "."]);
167
+ function normalizeModelForMatching(model) {
168
+ return model.trim().toLowerCase();
169
+ }
170
+ function modelLeafName(model) {
171
+ const normalized = normalizeModelForMatching(model);
172
+ const slash = normalized.lastIndexOf("/");
173
+ return slash >= 0 ? normalized.slice(slash + 1) : normalized;
174
+ }
175
+ function modelMatchesFamily(model, family) {
176
+ const name = modelLeafName(model);
177
+ const normalizedFamily = normalizeModelForMatching(family);
178
+ if (!name || !normalizedFamily) return false;
179
+ if (name === normalizedFamily) return true;
180
+ const delimiter = name[normalizedFamily.length];
181
+ return name.startsWith(normalizedFamily) && delimiter !== void 0 && MODEL_FAMILY_DELIMITERS.has(delimiter);
182
+ }
147
183
  function detectProvider(host) {
148
- host = host.toLowerCase();
149
- if (host.includes("openrouter")) return "openrouter";
150
- if (host.includes("gateway.ai.vercel")) return "vercel";
151
- if (host.includes("amazonaws") || host.includes("bedrock")) return "bedrock";
152
- if (host.includes("aiplatform.googleapis")) return "google-vertex";
153
- if (host.includes("api.x.ai")) return "xai";
154
- if (host.includes("groq")) return "groq";
155
- if (host.includes("fal.run") || host.includes("fal.ai")) return "fal";
156
- if (host.includes("deepinfra")) return "deepinfra";
157
- if (host.includes("bfl.ai")) return "black-forest-labs";
158
- if (host.includes("together")) return "together";
159
- if (host.includes("fireworks")) return "fireworks";
160
- if (host.includes("deepseek")) return "deepseek";
161
- if (host.includes("moonshot")) return "moonshotai";
162
- if (host.includes("perplexity")) return "perplexity";
163
- if (host.includes("dashscope") || host.includes("aliyuncs")) return "alibaba";
164
- if (host.includes("cerebras")) return "cerebras";
165
- if (host.includes("replicate")) return "replicate";
166
- if (host.includes("prodia")) return "prodia";
167
- if (host.includes("lumalabs") || host.includes("luma")) return "luma";
168
- if (host.includes("volces") || host.includes("bytedance")) return "bytedance";
169
- if (host.includes("kling")) return "kling";
170
- if (host.includes("elevenlabs")) return "elevenlabs";
171
- if (host.includes("assemblyai")) return "assemblyai";
172
- if (host.includes("deepgram")) return "deepgram";
173
- if (host.includes("gladia")) return "gladia";
174
- if (host.includes("lmnt")) return "lmnt";
175
- if (host.includes("hume")) return "hume";
176
- if (host.includes("rev.ai")) return "revai";
177
- if (host.includes("baseten")) return "baseten";
178
- if (host.includes("huggingface")) return "huggingface";
179
- if (host.includes("azure")) return "azure";
180
- if (host.includes("openai")) return "openai";
181
- if (host.includes("anthropic") || host.includes("claude")) return "anthropic";
182
- if (host.includes("googleapis") || host.includes("google")) return "google";
183
- if (host.includes("mistral")) return "mistral";
184
- if (host.includes("cohere")) return "cohere";
184
+ host = canonicalHostName(host);
185
+ if (hostMatches(host, "openrouter", "openrouter.ai")) return "openrouter";
186
+ if (hostMatches(host, "vercel", "gateway.ai.vercel.app", "gateway.ai.vercel.sh")) {
187
+ return "vercel";
188
+ }
189
+ if (hostMatches(host, "bedrock", "amazonaws.com") || hostHasLabelPrefix(host, "bedrock")) {
190
+ return "bedrock";
191
+ }
192
+ if (hostMatches(host, "aiplatform.googleapis.com")) return "google-vertex";
193
+ if (hostMatches(host, "xai", "x.ai", "api.x.ai")) return "xai";
194
+ if (hostMatches(host, "groq", "groq.com", "api.groq.com")) return "groq";
195
+ if (hostMatches(host, "fal", "fal.run", "fal.ai")) return "fal";
196
+ if (hostMatches(host, "deepinfra", "deepinfra.com")) return "deepinfra";
197
+ if (hostMatches(host, "bfl", "bfl.ai", "api.bfl.ai")) {
198
+ return "black-forest-labs";
199
+ }
200
+ if (hostMatches(host, "together", "together.xyz")) return "together";
201
+ if (hostMatches(host, "fireworks", "fireworks.ai")) return "fireworks";
202
+ if (hostMatches(host, "deepseek", "deepseek.com")) return "deepseek";
203
+ if (hostMatches(host, "moonshot", "moonshot.ai")) return "moonshotai";
204
+ if (hostMatches(host, "perplexity", "perplexity.ai")) return "perplexity";
205
+ if (hostMatches(host, "alibaba", "aliyuncs.com") || hostHasLabelPrefix(host, "dashscope")) {
206
+ return "alibaba";
207
+ }
208
+ if (hostMatches(host, "cerebras", "cerebras.ai")) return "cerebras";
209
+ if (hostMatches(host, "replicate", "replicate.com")) return "replicate";
210
+ if (hostMatches(host, "prodia", "prodia.com")) return "prodia";
211
+ if (hostMatches(host, "luma", "lumalabs.ai")) return "luma";
212
+ if (hostMatches(host, "bytedance", "volces.com") || hostHasLabel(host, "bytedance")) {
213
+ return "bytedance";
214
+ }
215
+ if (hostMatches(host, "kling", "klingai.com")) return "kling";
216
+ if (hostMatches(host, "elevenlabs", "elevenlabs.io")) return "elevenlabs";
217
+ if (hostMatches(host, "assemblyai", "assemblyai.com")) return "assemblyai";
218
+ if (hostMatches(host, "deepgram", "deepgram.com")) return "deepgram";
219
+ if (hostMatches(host, "gladia", "gladia.io")) return "gladia";
220
+ if (hostMatches(host, "lmnt", "lmnt.com")) return "lmnt";
221
+ if (hostMatches(host, "hume", "hume.ai")) return "hume";
222
+ if (hostMatches(host, "revai", "rev.ai")) return "revai";
223
+ if (hostMatches(host, "baseten", "baseten.co")) return "baseten";
224
+ if (hostMatches(host, "huggingface", "huggingface.co")) return "huggingface";
225
+ if (hostMatches(host, "azure", "azure.com")) return "azure";
226
+ if (hostMatches(host, "openai", "openai.com")) return "openai";
227
+ if (hostMatches(host, "anthropic", "anthropic.com", "claude.ai")) {
228
+ return "anthropic";
229
+ }
230
+ if (hostMatches(host, "google", "googleapis.com")) return "google";
231
+ if (hostMatches(host, "mistral", "mistral.ai")) return "mistral";
232
+ if (hostMatches(host, "cohere", "cohere.com")) return "cohere";
185
233
  return void 0;
186
234
  }
187
235
  var ALIASES = {
@@ -224,7 +272,10 @@ var ALIASES = {
224
272
  num_completions: "n",
225
273
  // effort / reasoning
226
274
  reasoning_effort: "effort",
275
+ reasoningEffort: "effort",
227
276
  reasoning: "effort",
277
+ thinking_effort: "effort",
278
+ thinkingEffort: "effort",
228
279
  // cache
229
280
  cache_control: "cache",
230
281
  cacheControl: "cache",
@@ -234,6 +285,7 @@ var ALIASES = {
234
285
  var OPENAI_COMPATIBLE_PARAMS = {
235
286
  temperature: "temperature",
236
287
  max_tokens: "max_tokens",
288
+ max_completion_tokens: "max_completion_tokens",
237
289
  top_p: "top_p",
238
290
  top_k: "top_k",
239
291
  frequency_penalty: "frequency_penalty",
@@ -244,6 +296,90 @@ var OPENAI_COMPATIBLE_PARAMS = {
244
296
  stream: "stream",
245
297
  effort: "reasoning_effort"
246
298
  };
299
+ var COMMON_IMAGE_PARAMS = {
300
+ prompt: "prompt",
301
+ negative_prompt: "negative_prompt",
302
+ seed: "seed",
303
+ image_size: "image_size",
304
+ aspect_ratio: "aspect_ratio",
305
+ output_format: "output_format",
306
+ width: "width",
307
+ height: "height"
308
+ };
309
+ var FAL_PARAMS = {
310
+ ...COMMON_IMAGE_PARAMS,
311
+ num_images: "num_images",
312
+ enable_safety_checker: "enable_safety_checker",
313
+ enable_safety_checks: "enable_safety_checks",
314
+ enable_prompt_expansion: "enable_prompt_expansion",
315
+ expand_prompt: "expand_prompt"
316
+ };
317
+ var REPLICATE_PARAMS = {
318
+ ...COMMON_IMAGE_PARAMS,
319
+ input: "input",
320
+ version: "version",
321
+ num_outputs: "num_outputs",
322
+ num_inference_steps: "num_inference_steps",
323
+ guidance_scale: "guidance_scale",
324
+ stream: "stream",
325
+ webhook: "webhook",
326
+ webhook_events_filter: "webhook_events_filter"
327
+ };
328
+ var PRODIA_PARAMS = {
329
+ ...COMMON_IMAGE_PARAMS,
330
+ model: "model",
331
+ style_preset: "style_preset",
332
+ steps: "steps",
333
+ cfg_scale: "cfg_scale",
334
+ upscale: "upscale",
335
+ sampler: "sampler",
336
+ type: "type",
337
+ config: "config",
338
+ price: "price"
339
+ };
340
+ var LUMA_PARAMS = {
341
+ prompt: "prompt",
342
+ model: "model",
343
+ aspect_ratio: "aspect_ratio",
344
+ keyframes: "keyframes",
345
+ loop: "loop",
346
+ duration: "duration",
347
+ type: "type",
348
+ image_ref: "image_ref",
349
+ video: "video",
350
+ source: "source"
351
+ };
352
+ var OPENROUTER_ROUTING_PARAMS = {
353
+ provider: "provider",
354
+ order: "order",
355
+ "provider.order": "provider.order",
356
+ only: "only",
357
+ "provider.only": "provider.only",
358
+ ignore: "ignore",
359
+ "provider.ignore": "provider.ignore",
360
+ allow_fallbacks: "allow_fallbacks",
361
+ "provider.allow_fallbacks": "provider.allow_fallbacks",
362
+ require_parameters: "require_parameters",
363
+ "provider.require_parameters": "provider.require_parameters",
364
+ data_collection: "data_collection",
365
+ "provider.data_collection": "provider.data_collection",
366
+ zdr: "zdr",
367
+ "provider.zdr": "provider.zdr",
368
+ enforce_distillable_text: "enforce_distillable_text",
369
+ "provider.enforce_distillable_text": "provider.enforce_distillable_text",
370
+ quantizations: "quantizations",
371
+ "provider.quantizations": "provider.quantizations",
372
+ sort: "sort",
373
+ "provider.sort": "provider.sort",
374
+ preferred_min_throughput: "preferred_min_throughput",
375
+ "provider.preferred_min_throughput": "provider.preferred_min_throughput",
376
+ preferred_max_latency: "preferred_max_latency",
377
+ "provider.preferred_max_latency": "provider.preferred_max_latency",
378
+ max_price: "max_price",
379
+ "provider.max_price": "provider.max_price",
380
+ transforms: "transforms",
381
+ plugins: "plugins"
382
+ };
247
383
  var GOOGLE_COMPATIBLE_PARAMS = {
248
384
  temperature: "temperature",
249
385
  max_tokens: "maxOutputTokens",
@@ -262,6 +398,7 @@ var PROVIDER_PARAMS = {
262
398
  openai: {
263
399
  temperature: "temperature",
264
400
  max_tokens: "max_tokens",
401
+ max_completion_tokens: "max_completion_tokens",
265
402
  top_p: "top_p",
266
403
  frequency_penalty: "frequency_penalty",
267
404
  presence_penalty: "presence_penalty",
@@ -338,6 +475,7 @@ var PROVIDER_PARAMS = {
338
475
  // OpenAI-compatible API with extra routing params
339
476
  temperature: "temperature",
340
477
  max_tokens: "max_tokens",
478
+ max_completion_tokens: "max_completion_tokens",
341
479
  top_p: "top_p",
342
480
  top_k: "top_k",
343
481
  frequency_penalty: "frequency_penalty",
@@ -346,12 +484,14 @@ var PROVIDER_PARAMS = {
346
484
  n: "n",
347
485
  seed: "seed",
348
486
  stream: "stream",
349
- effort: "reasoning_effort"
487
+ effort: "reasoning_effort",
488
+ ...OPENROUTER_ROUTING_PARAMS
350
489
  },
351
490
  vercel: {
352
491
  // OpenAI-compatible gateway
353
492
  temperature: "temperature",
354
493
  max_tokens: "max_tokens",
494
+ max_completion_tokens: "max_completion_tokens",
355
495
  top_p: "top_p",
356
496
  top_k: "top_k",
357
497
  frequency_penalty: "frequency_penalty",
@@ -364,7 +504,7 @@ var PROVIDER_PARAMS = {
364
504
  },
365
505
  xai: OPENAI_COMPATIBLE_PARAMS,
366
506
  groq: OPENAI_COMPATIBLE_PARAMS,
367
- fal: {},
507
+ fal: FAL_PARAMS,
368
508
  deepinfra: OPENAI_COMPATIBLE_PARAMS,
369
509
  "black-forest-labs": {},
370
510
  together: OPENAI_COMPATIBLE_PARAMS,
@@ -374,9 +514,9 @@ var PROVIDER_PARAMS = {
374
514
  perplexity: OPENAI_COMPATIBLE_PARAMS,
375
515
  alibaba: OPENAI_COMPATIBLE_PARAMS,
376
516
  cerebras: OPENAI_COMPATIBLE_PARAMS,
377
- replicate: {},
378
- prodia: {},
379
- luma: {},
517
+ replicate: REPLICATE_PARAMS,
518
+ prodia: PRODIA_PARAMS,
519
+ luma: LUMA_PARAMS,
380
520
  bytedance: {},
381
521
  kling: {},
382
522
  elevenlabs: {},
@@ -389,6 +529,15 @@ var PROVIDER_PARAMS = {
389
529
  baseten: OPENAI_COMPATIBLE_PARAMS,
390
530
  huggingface: OPENAI_COMPATIBLE_PARAMS
391
531
  };
532
+ var REASONING_EFFORT_VALUES = [
533
+ "none",
534
+ "minimal",
535
+ "low",
536
+ "medium",
537
+ "high",
538
+ "xhigh",
539
+ "max"
540
+ ];
392
541
  var OPENAI_COMPATIBLE_PARAM_SPECS = {
393
542
  temperature: {
394
543
  type: "number",
@@ -403,6 +552,12 @@ var OPENAI_COMPATIBLE_PARAM_SPECS = {
403
552
  default: 4096,
404
553
  description: "Maximum output tokens"
405
554
  },
555
+ max_completion_tokens: {
556
+ type: "number",
557
+ min: 1,
558
+ default: 4096,
559
+ description: "Maximum completion tokens (reasoning models)"
560
+ },
406
561
  top_p: {
407
562
  type: "number",
408
563
  min: 0,
@@ -436,11 +591,221 @@ var OPENAI_COMPATIBLE_PARAM_SPECS = {
436
591
  stream: { type: "boolean", default: false, description: "Stream response" },
437
592
  reasoning_effort: {
438
593
  type: "string",
439
- values: ["none", "minimal", "low", "medium", "high", "xhigh"],
594
+ values: REASONING_EFFORT_VALUES,
440
595
  default: "medium",
441
596
  description: "Reasoning effort"
442
597
  }
443
598
  };
599
+ var OPENROUTER_ROUTING_PARAM_SPECS = {
600
+ provider: { type: "string", description: "Provider routing preferences" },
601
+ order: { type: "string", description: "Provider order" },
602
+ "provider.order": { type: "string", description: "Provider order" },
603
+ only: { type: "string", description: "Provider allowlist" },
604
+ "provider.only": { type: "string", description: "Provider allowlist" },
605
+ ignore: { type: "string", description: "Provider blocklist" },
606
+ "provider.ignore": { type: "string", description: "Provider blocklist" },
607
+ allow_fallbacks: {
608
+ type: "boolean",
609
+ default: true,
610
+ description: "Allow fallback providers"
611
+ },
612
+ "provider.allow_fallbacks": {
613
+ type: "boolean",
614
+ default: true,
615
+ description: "Allow fallback providers"
616
+ },
617
+ require_parameters: {
618
+ type: "boolean",
619
+ default: false,
620
+ description: "Only route to providers that support all request params"
621
+ },
622
+ "provider.require_parameters": {
623
+ type: "boolean",
624
+ default: false,
625
+ description: "Only route to providers that support all request params"
626
+ },
627
+ data_collection: {
628
+ type: "string",
629
+ values: ["allow", "deny"],
630
+ default: "allow",
631
+ description: "Provider data collection policy"
632
+ },
633
+ "provider.data_collection": {
634
+ type: "string",
635
+ values: ["allow", "deny"],
636
+ default: "allow",
637
+ description: "Provider data collection policy"
638
+ },
639
+ zdr: {
640
+ type: "boolean",
641
+ description: "Require zero data retention providers"
642
+ },
643
+ "provider.zdr": {
644
+ type: "boolean",
645
+ description: "Require zero data retention providers"
646
+ },
647
+ enforce_distillable_text: {
648
+ type: "boolean",
649
+ description: "Require providers that allow text distillation"
650
+ },
651
+ "provider.enforce_distillable_text": {
652
+ type: "boolean",
653
+ description: "Require providers that allow text distillation"
654
+ },
655
+ quantizations: {
656
+ type: "string",
657
+ description: "Allowed provider quantization levels"
658
+ },
659
+ "provider.quantizations": {
660
+ type: "string",
661
+ description: "Allowed provider quantization levels"
662
+ },
663
+ sort: {
664
+ type: "string",
665
+ values: ["price", "throughput", "latency", "cost", "ttft", "tps"],
666
+ description: "Provider sort strategy"
667
+ },
668
+ "provider.sort": {
669
+ type: "string",
670
+ values: ["price", "throughput", "latency", "cost", "ttft", "tps"],
671
+ description: "Provider sort strategy"
672
+ },
673
+ preferred_min_throughput: {
674
+ type: "number",
675
+ min: 0,
676
+ description: "Preferred minimum provider throughput"
677
+ },
678
+ "provider.preferred_min_throughput": {
679
+ type: "number",
680
+ min: 0,
681
+ description: "Preferred minimum provider throughput"
682
+ },
683
+ preferred_max_latency: {
684
+ type: "number",
685
+ min: 0,
686
+ description: "Preferred maximum provider latency"
687
+ },
688
+ "provider.preferred_max_latency": {
689
+ type: "number",
690
+ min: 0,
691
+ description: "Preferred maximum provider latency"
692
+ },
693
+ max_price: {
694
+ type: "string",
695
+ description: "Maximum provider price filter"
696
+ },
697
+ "provider.max_price": {
698
+ type: "string",
699
+ description: "Maximum provider price filter"
700
+ },
701
+ transforms: {
702
+ type: "string",
703
+ description: "Legacy OpenRouter message transforms"
704
+ },
705
+ plugins: {
706
+ type: "string",
707
+ description: "OpenRouter request plugins"
708
+ }
709
+ };
710
+ var FAL_PARAM_SPECS = {
711
+ prompt: { type: "string", description: "Prompt" },
712
+ negative_prompt: { type: "string", description: "Negative prompt" },
713
+ seed: { type: "number", description: "Random seed" },
714
+ num_images: {
715
+ type: "number",
716
+ min: 1,
717
+ description: "Number of images to generate"
718
+ },
719
+ image_size: { type: "string", description: "Output image size" },
720
+ aspect_ratio: { type: "string", description: "Output aspect ratio" },
721
+ enable_safety_checker: {
722
+ type: "boolean",
723
+ description: "Enable safety checker"
724
+ },
725
+ enable_safety_checks: {
726
+ type: "boolean",
727
+ description: "Enable safety checker"
728
+ },
729
+ enable_prompt_expansion: {
730
+ type: "boolean",
731
+ description: "Enable prompt expansion"
732
+ },
733
+ expand_prompt: {
734
+ type: "boolean",
735
+ description: "Enable prompt expansion"
736
+ },
737
+ output_format: {
738
+ type: "string",
739
+ values: ["jpeg", "jpg", "png", "webp", "gif"],
740
+ description: "Output image format"
741
+ },
742
+ width: { type: "number", min: 1, description: "Image width" },
743
+ height: { type: "number", min: 1, description: "Image height" }
744
+ };
745
+ var REPLICATE_PARAM_SPECS = {
746
+ prompt: { type: "string", description: "Prompt" },
747
+ negative_prompt: { type: "string", description: "Negative prompt" },
748
+ input: { type: "string", description: "Model input object" },
749
+ version: { type: "string", description: "Model version" },
750
+ seed: { type: "number", description: "Random seed" },
751
+ num_outputs: {
752
+ type: "number",
753
+ min: 1,
754
+ description: "Number of outputs to generate"
755
+ },
756
+ num_inference_steps: {
757
+ type: "number",
758
+ min: 1,
759
+ description: "Number of inference steps"
760
+ },
761
+ guidance_scale: {
762
+ type: "number",
763
+ min: 0,
764
+ description: "Guidance scale"
765
+ },
766
+ image_size: { type: "string", description: "Output image size" },
767
+ aspect_ratio: { type: "string", description: "Output aspect ratio" },
768
+ output_format: { type: "string", description: "Output format" },
769
+ width: { type: "number", min: 1, description: "Image width" },
770
+ height: { type: "number", min: 1, description: "Image height" },
771
+ stream: { type: "boolean", description: "Request streaming output" },
772
+ webhook: { type: "string", description: "Webhook URL" },
773
+ webhook_events_filter: {
774
+ type: "string",
775
+ description: "Webhook events filter"
776
+ }
777
+ };
778
+ var PRODIA_PARAM_SPECS = {
779
+ model: { type: "string", description: "Model name" },
780
+ prompt: { type: "string", description: "Prompt" },
781
+ negative_prompt: { type: "string", description: "Negative prompt" },
782
+ style_preset: { type: "string", description: "Style preset" },
783
+ steps: { type: "number", min: 1, description: "Generation steps" },
784
+ cfg_scale: { type: "number", min: 0, description: "CFG scale" },
785
+ seed: { type: "number", description: "Random seed" },
786
+ upscale: { type: "boolean", description: "Enable 2x upscale" },
787
+ sampler: { type: "string", description: "Sampler" },
788
+ width: { type: "number", min: 1, max: 1024, description: "Image width" },
789
+ height: { type: "number", min: 1, max: 1024, description: "Image height" },
790
+ image_size: { type: "string", description: "Output image size" },
791
+ aspect_ratio: { type: "string", description: "Output aspect ratio" },
792
+ output_format: { type: "string", description: "Output format" },
793
+ type: { type: "string", description: "Prodia v2 job type" },
794
+ config: { type: "string", description: "Prodia v2 job config" },
795
+ price: { type: "boolean", description: "Include Prodia v2 job price" }
796
+ };
797
+ var LUMA_PARAM_SPECS = {
798
+ prompt: { type: "string", description: "Prompt" },
799
+ model: { type: "string", description: "Model name" },
800
+ aspect_ratio: { type: "string", description: "Output aspect ratio" },
801
+ keyframes: { type: "string", description: "Generation keyframes" },
802
+ loop: { type: "boolean", description: "Generate a looping video" },
803
+ duration: { type: "string", description: "Generation duration" },
804
+ type: { type: "string", description: "Generation type" },
805
+ image_ref: { type: "string", description: "Image reference" },
806
+ video: { type: "string", description: "Video options" },
807
+ source: { type: "string", description: "Source generation or media" }
808
+ };
444
809
  var GOOGLE_COMPATIBLE_PARAM_SPECS = {
445
810
  temperature: {
446
811
  type: "number",
@@ -509,6 +874,12 @@ var PARAM_SPECS = {
509
874
  default: 4096,
510
875
  description: "Maximum output tokens"
511
876
  },
877
+ max_completion_tokens: {
878
+ type: "number",
879
+ min: 1,
880
+ default: 4096,
881
+ description: "Maximum completion tokens (reasoning models)"
882
+ },
512
883
  top_p: {
513
884
  type: "number",
514
885
  min: 0,
@@ -536,7 +907,7 @@ var PARAM_SPECS = {
536
907
  stream: { type: "boolean", default: false, description: "Stream response" },
537
908
  reasoning_effort: {
538
909
  type: "string",
539
- values: ["none", "minimal", "low", "medium", "high", "xhigh"],
910
+ values: REASONING_EFFORT_VALUES,
540
911
  default: "medium",
541
912
  description: "Reasoning effort"
542
913
  }
@@ -573,8 +944,8 @@ var PARAM_SPECS = {
573
944
  stream: { type: "boolean", default: false, description: "Stream response" },
574
945
  effort: {
575
946
  type: "string",
576
- values: ["low", "medium", "high", "max"],
577
- default: "medium",
947
+ values: REASONING_EFFORT_VALUES,
948
+ default: "low",
578
949
  description: "Thinking effort"
579
950
  },
580
951
  cache_control: {
@@ -799,6 +1170,12 @@ var PARAM_SPECS = {
799
1170
  default: 4096,
800
1171
  description: "Maximum output tokens"
801
1172
  },
1173
+ max_completion_tokens: {
1174
+ type: "number",
1175
+ min: 1,
1176
+ default: 4096,
1177
+ description: "Maximum completion tokens (reasoning models)"
1178
+ },
802
1179
  top_p: {
803
1180
  type: "number",
804
1181
  min: 0,
@@ -832,10 +1209,11 @@ var PARAM_SPECS = {
832
1209
  stream: { type: "boolean", default: false, description: "Stream response" },
833
1210
  reasoning_effort: {
834
1211
  type: "string",
835
- values: ["none", "minimal", "low", "medium", "high", "xhigh"],
1212
+ values: REASONING_EFFORT_VALUES,
836
1213
  default: "medium",
837
1214
  description: "Reasoning effort"
838
- }
1215
+ },
1216
+ ...OPENROUTER_ROUTING_PARAM_SPECS
839
1217
  },
840
1218
  vercel: {
841
1219
  // Loose validation — proxies to many providers with varying ranges
@@ -852,6 +1230,12 @@ var PARAM_SPECS = {
852
1230
  default: 4096,
853
1231
  description: "Maximum output tokens"
854
1232
  },
1233
+ max_completion_tokens: {
1234
+ type: "number",
1235
+ min: 1,
1236
+ default: 4096,
1237
+ description: "Maximum completion tokens (reasoning models)"
1238
+ },
855
1239
  top_p: {
856
1240
  type: "number",
857
1241
  min: 0,
@@ -885,14 +1269,70 @@ var PARAM_SPECS = {
885
1269
  stream: { type: "boolean", default: false, description: "Stream response" },
886
1270
  reasoning_effort: {
887
1271
  type: "string",
888
- values: ["none", "minimal", "low", "medium", "high", "xhigh"],
1272
+ values: REASONING_EFFORT_VALUES,
889
1273
  default: "medium",
890
1274
  description: "Reasoning effort"
1275
+ },
1276
+ order: { type: "string", description: "Gateway provider order" },
1277
+ only: { type: "string", description: "Gateway provider allowlist" },
1278
+ models: { type: "string", description: "Gateway fallback models" },
1279
+ tags: { type: "string", description: "Gateway usage tags" },
1280
+ sort: {
1281
+ type: "string",
1282
+ values: ["cost", "ttft", "tps"],
1283
+ description: "Gateway provider sort strategy"
1284
+ },
1285
+ caching: {
1286
+ type: "string",
1287
+ values: ["auto"],
1288
+ description: "Gateway automatic caching strategy"
1289
+ },
1290
+ user: { type: "string", description: "Gateway usage user identifier" },
1291
+ byok: { type: "string", description: "Gateway BYOK credentials" },
1292
+ zero_data_retention: {
1293
+ type: "boolean",
1294
+ description: "Gateway zero data retention routing"
1295
+ },
1296
+ zeroDataRetention: {
1297
+ type: "boolean",
1298
+ description: "Gateway zero data retention routing"
1299
+ },
1300
+ disallow_prompt_training: {
1301
+ type: "boolean",
1302
+ description: "Gateway prompt training opt-out routing"
1303
+ },
1304
+ disallowPromptTraining: {
1305
+ type: "boolean",
1306
+ description: "Gateway prompt training opt-out routing"
1307
+ },
1308
+ hipaa_compliant: {
1309
+ type: "boolean",
1310
+ description: "Gateway HIPAA-compliant routing"
1311
+ },
1312
+ hipaaCompliant: {
1313
+ type: "boolean",
1314
+ description: "Gateway HIPAA-compliant routing"
1315
+ },
1316
+ quota_entity_id: {
1317
+ type: "string",
1318
+ description: "Gateway quota entity identifier"
1319
+ },
1320
+ quotaEntityId: {
1321
+ type: "string",
1322
+ description: "Gateway quota entity identifier"
1323
+ },
1324
+ provider_timeouts: {
1325
+ type: "string",
1326
+ description: "Gateway provider timeouts"
1327
+ },
1328
+ providerTimeouts: {
1329
+ type: "string",
1330
+ description: "Gateway provider timeouts"
891
1331
  }
892
1332
  },
893
1333
  xai: OPENAI_COMPATIBLE_PARAM_SPECS,
894
1334
  groq: OPENAI_COMPATIBLE_PARAM_SPECS,
895
- fal: {},
1335
+ fal: FAL_PARAM_SPECS,
896
1336
  deepinfra: OPENAI_COMPATIBLE_PARAM_SPECS,
897
1337
  "black-forest-labs": {},
898
1338
  together: OPENAI_COMPATIBLE_PARAM_SPECS,
@@ -902,9 +1342,9 @@ var PARAM_SPECS = {
902
1342
  perplexity: OPENAI_COMPATIBLE_PARAM_SPECS,
903
1343
  alibaba: OPENAI_COMPATIBLE_PARAM_SPECS,
904
1344
  cerebras: OPENAI_COMPATIBLE_PARAM_SPECS,
905
- replicate: {},
906
- prodia: {},
907
- luma: {},
1345
+ replicate: REPLICATE_PARAM_SPECS,
1346
+ prodia: PRODIA_PARAM_SPECS,
1347
+ luma: LUMA_PARAM_SPECS,
908
1348
  bytedance: {},
909
1349
  kling: {},
910
1350
  elevenlabs: {},
@@ -918,11 +1358,13 @@ var PARAM_SPECS = {
918
1358
  huggingface: OPENAI_COMPATIBLE_PARAM_SPECS
919
1359
  };
920
1360
  function isReasoningModel(model) {
921
- const name = model.includes("/") ? model.split("/").pop() : model;
922
- return /^o[134]/.test(name);
1361
+ const name = modelLeafName(model);
1362
+ const oSeries = name.match(/^o\d+/)?.[0];
1363
+ const gptSeries = name.match(/^gpt-[5-9]/)?.[0];
1364
+ return (oSeries ? modelMatchesFamily(name, oSeries) : false) || (gptSeries ? modelMatchesFamily(name, gptSeries) : false);
923
1365
  }
924
1366
  function canHostOpenAIModels(provider) {
925
- return provider === "openai" || provider === "openrouter" || provider === "vercel";
1367
+ return provider === "openai" || provider === "azure" || provider === "openrouter" || provider === "vercel";
926
1368
  }
927
1369
  function isGatewayProvider(provider) {
928
1370
  return provider === "openrouter" || provider === "vercel";
@@ -930,29 +1372,27 @@ function isGatewayProvider(provider) {
930
1372
  function detectGatewaySubProvider(model) {
931
1373
  const slash = model.indexOf("/");
932
1374
  if (slash < 1) return void 0;
933
- const prefix = model.slice(0, slash);
934
- const direct = [
935
- "openai",
936
- "anthropic",
937
- "google",
938
- "mistral",
939
- "cohere"
940
- ];
941
- return direct.find((p) => p === prefix);
1375
+ const prefix = normalizeModelForMatching(model.slice(0, slash));
1376
+ const direct = {
1377
+ openai: "openai",
1378
+ anthropic: "anthropic",
1379
+ google: "google",
1380
+ vertex: "google",
1381
+ "google-vertex": "google",
1382
+ mistral: "mistral",
1383
+ cohere: "cohere"
1384
+ };
1385
+ return direct[prefix];
942
1386
  }
943
1387
  var REASONING_MODEL_UNSUPPORTED = /* @__PURE__ */ new Set([
944
1388
  "temperature",
945
1389
  "top_p",
1390
+ "top_k",
946
1391
  "frequency_penalty",
947
1392
  "presence_penalty",
948
1393
  "n"
949
1394
  ]);
950
1395
  function detectBedrockModelFamily(model) {
951
- const parts = model.split(".");
952
- let prefix = parts[0];
953
- if (["us", "eu", "apac", "global"].includes(prefix) && parts.length > 1) {
954
- prefix = parts[1];
955
- }
956
1396
  const families = [
957
1397
  "anthropic",
958
1398
  "meta",
@@ -961,12 +1401,18 @@ function detectBedrockModelFamily(model) {
961
1401
  "cohere",
962
1402
  "ai21"
963
1403
  ];
964
- return families.find((f) => prefix === f);
1404
+ const parts = modelLeafName(model).split(".");
1405
+ return families.find((family) => parts.includes(family));
965
1406
  }
966
1407
  function bedrockSupportsCaching(model) {
967
1408
  const family = detectBedrockModelFamily(model);
968
1409
  if (family === "anthropic") return true;
969
- if (family === "amazon" && model.includes("nova")) return true;
1410
+ if (family === "amazon") {
1411
+ const parts = modelLeafName(model).split(".");
1412
+ const amazonIndex = parts.indexOf("amazon");
1413
+ const modelName = amazonIndex >= 0 ? parts[amazonIndex + 1] : void 0;
1414
+ return modelName ? modelMatchesFamily(modelName, "nova") : false;
1415
+ }
970
1416
  return false;
971
1417
  }
972
1418
  var CACHE_VALUES = {
@@ -1051,7 +1497,6 @@ var CACHE_TTLS = {
1051
1497
  baseten: void 0,
1052
1498
  huggingface: void 0
1053
1499
  };
1054
- var DURATION_RE = /^\d+[mh]$/;
1055
1500
 
1056
1501
  // src/parse.ts
1057
1502
  function parse(connectionString) {
@@ -1088,6 +1533,7 @@ function build(config) {
1088
1533
  }
1089
1534
 
1090
1535
  // src/normalize.ts
1536
+ var DURATION_RE = /^\d+[mh]$/;
1091
1537
  function normalize(config, options = {}) {
1092
1538
  const provider = (config.hostAlias ? providerFromHostAlias(config.hostAlias) : void 0) ?? detectProvider(config.host);
1093
1539
  const subProvider = provider && isGatewayProvider(provider) ? detectGatewaySubProvider(config.model) : void 0;
@@ -1175,6 +1621,17 @@ function normalize(config, options = {}) {
1175
1621
  }
1176
1622
  key = "max_completion_tokens";
1177
1623
  }
1624
+ if (provider && canHostOpenAIModels(provider) && isReasoningModel(config.model) && REASONING_MODEL_UNSUPPORTED.has(key)) {
1625
+ if (options.verbose) {
1626
+ changes.push({
1627
+ from: key,
1628
+ to: "(dropped)",
1629
+ value,
1630
+ reason: `${provider} reasoning model "${config.model}" does not support "${key}"`
1631
+ });
1632
+ }
1633
+ continue;
1634
+ }
1178
1635
  params[key] = value;
1179
1636
  }
1180
1637
  return {
@@ -1233,15 +1690,8 @@ function validate(connectionString, options = {}) {
1233
1690
  const gatewayReverseMap = subProvider ? buildReverseParamMap(provider) : void 0;
1234
1691
  const knownParams = subProvider ? buildSubProviderKnownParams(provider, subProvider) : new Set(Object.values(PROVIDER_PARAMS[provider]));
1235
1692
  for (const [key, value] of Object.entries(config.params)) {
1236
- if (canHostOpenAIModels(provider) && isReasoningModel(config.model) && REASONING_MODEL_UNSUPPORTED.has(key)) {
1237
- issues.push({
1238
- param: key,
1239
- value,
1240
- message: `"${key}" is not supported by OpenAI reasoning model "${config.model}". Use "reasoning_effort" instead of temperature for controlling output.`,
1241
- severity: "error"
1242
- });
1243
- continue;
1244
- }
1693
+ const subProviderSpecResult = subProvider && gatewayReverseMap ? lookupSubProviderSpec(key, gatewayReverseMap, subProvider) : void 0;
1694
+ const gatewayOwnSpec = subProvider && !subProviderSpecResult?.spec ? PARAM_SPECS[provider]?.[key] : void 0;
1245
1695
  if (provider === "bedrock") {
1246
1696
  const family = detectBedrockModelFamily(config.model);
1247
1697
  if (key === "topK" && family && family !== "anthropic" && family !== "cohere" && family !== "mistral") {
@@ -1263,20 +1713,18 @@ function validate(connectionString, options = {}) {
1263
1713
  continue;
1264
1714
  }
1265
1715
  }
1266
- if (!knownParams.has(key) && !specs[key]) {
1267
- issues.push({
1268
- param: key,
1269
- value,
1270
- message: `Unknown param "${key}" for ${effectiveProvider}.`,
1271
- severity: options.strict ? "error" : "warning"
1272
- });
1716
+ if (!knownParams.has(key) && !specs[key] && !gatewayOwnSpec) {
1717
+ if (options.strict) {
1718
+ issues.push({
1719
+ param: key,
1720
+ value,
1721
+ message: `Unknown param "${key}" for ${effectiveProvider}.`,
1722
+ severity: "error"
1723
+ });
1724
+ }
1273
1725
  continue;
1274
1726
  }
1275
- let spec = specs[key];
1276
- if (subProvider && gatewayReverseMap && !spec) {
1277
- const result = lookupSubProviderSpec(key, gatewayReverseMap, subProvider);
1278
- spec = result.spec;
1279
- }
1727
+ const spec = subProviderSpecResult?.spec ?? gatewayOwnSpec ?? specs[key];
1280
1728
  if (!spec) continue;
1281
1729
  if ((effectiveProvider === "anthropic" || provider === "bedrock" && detectBedrockModelFamily(config.model) === "anthropic") && (key === "temperature" || key === "top_p" || key === "topP")) {
1282
1730
  const otherKey = key === "temperature" ? provider === "bedrock" ? "topP" : "top_p" : "temperature";