llm-strings 1.1.2 → 1.2.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.
Files changed (46) hide show
  1. package/README.md +117 -45
  2. package/dist/ai-sdk.cjs +831 -0
  3. package/dist/ai-sdk.cjs.map +1 -0
  4. package/dist/ai-sdk.d.cts +27 -0
  5. package/dist/ai-sdk.d.ts +27 -0
  6. package/dist/ai-sdk.js +465 -0
  7. package/dist/ai-sdk.js.map +1 -0
  8. package/dist/{chunk-MPIHGH6L.js → chunk-2ARD4TFU.js} +5 -4
  9. package/dist/chunk-2ARD4TFU.js.map +1 -0
  10. package/dist/{chunk-FCEV23OT.js → chunk-BCOUH7LH.js} +9 -3
  11. package/dist/chunk-BCOUH7LH.js.map +1 -0
  12. package/dist/{chunk-UYMVUTLV.js → chunk-RPXK2A7O.js} +5 -9
  13. package/dist/chunk-RPXK2A7O.js.map +1 -0
  14. package/dist/{chunk-XID353H7.js → chunk-W4NIQY7M.js} +412 -52
  15. package/dist/chunk-W4NIQY7M.js.map +1 -0
  16. package/dist/index.cjs +443 -84
  17. package/dist/index.cjs.map +1 -1
  18. package/dist/index.d.cts +1 -1
  19. package/dist/index.d.ts +4 -4
  20. package/dist/index.js +9 -4
  21. package/dist/normalize.cjs +19 -2
  22. package/dist/normalize.cjs.map +1 -1
  23. package/dist/normalize.d.cts +1 -1
  24. package/dist/normalize.d.ts +2 -2
  25. package/dist/normalize.js +2 -2
  26. package/dist/parse.cjs +73 -2
  27. package/dist/parse.cjs.map +1 -1
  28. package/dist/parse.d.cts +5 -1
  29. package/dist/parse.d.ts +5 -1
  30. package/dist/parse.js +2 -1
  31. package/dist/{provider-core-DinpG40u.d.cts → provider-core-BiAl8MCV.d.cts} +20 -1
  32. package/dist/{provider-core-DinpG40u.d.ts → provider-core-BiAl8MCV.d.ts} +20 -1
  33. package/dist/providers.cjs +786 -110
  34. package/dist/providers.cjs.map +1 -1
  35. package/dist/providers.d.cts +2 -2
  36. package/dist/providers.d.ts +2 -2
  37. package/dist/providers.js +379 -60
  38. package/dist/providers.js.map +1 -1
  39. package/dist/validate.cjs +433 -79
  40. package/dist/validate.cjs.map +1 -1
  41. package/dist/validate.js +4 -4
  42. package/package.json +13 -3
  43. package/dist/chunk-FCEV23OT.js.map +0 -1
  44. package/dist/chunk-MPIHGH6L.js.map +0 -1
  45. package/dist/chunk-UYMVUTLV.js.map +0 -1
  46. package/dist/chunk-XID353H7.js.map +0 -1
package/dist/validate.cjs CHANGED
@@ -24,34 +24,81 @@ __export(validate_exports, {
24
24
  });
25
25
  module.exports = __toCommonJS(validate_exports);
26
26
 
27
- // src/parse.ts
28
- function parse(connectionString) {
29
- const url = new URL(connectionString);
30
- if (url.protocol !== "llm:") {
31
- throw new Error(
32
- `Invalid scheme: expected "llm://", got "${url.protocol}//"`
33
- );
27
+ // src/provider-core.ts
28
+ function hasOwn(object, key) {
29
+ return Object.prototype.hasOwnProperty.call(object, key);
30
+ }
31
+ var HOST_ALIASES = {
32
+ openai: "api.openai.com",
33
+ anthropic: "api.anthropic.com",
34
+ google: "generativelanguage.googleapis.com",
35
+ aistudio: "generativelanguage.googleapis.com",
36
+ mistral: "api.mistral.ai",
37
+ cohere: "api.cohere.com",
38
+ bedrock: "bedrock-runtime.us-east-1.amazonaws.com",
39
+ openrouter: "openrouter.ai",
40
+ vercel: "gateway.ai.vercel.app",
41
+ alibaba: "dashscope-intl.aliyuncs.com",
42
+ alibabacloud: "dashscope-intl.aliyuncs.com",
43
+ dashscope: "dashscope-intl.aliyuncs.com",
44
+ fireworks: "api.fireworks.ai",
45
+ fireworksai: "api.fireworks.ai",
46
+ venice: "api.venice.ai",
47
+ parasail: "api.parasail.io",
48
+ deepinfra: "api.deepinfra.com",
49
+ atlascloud: "api.atlascloud.ai",
50
+ novita: "api.novita.ai",
51
+ novitaai: "api.novita.ai",
52
+ grok: "api.x.ai",
53
+ xai: "api.x.ai",
54
+ wandb: "api.inference.wandb.ai",
55
+ weightsandbiases: "api.inference.wandb.ai",
56
+ baidu: "qianfan.baidubce.com",
57
+ qianfan: "qianfan.baidubce.com",
58
+ vertex: "aiplatform.googleapis.com",
59
+ xiaomi: "api.xiaomimimo.com",
60
+ minimax: "api.minimax.io"
61
+ };
62
+ function readProcessEnv() {
63
+ return typeof process !== "undefined" && process.env ? process.env : {};
64
+ }
65
+ function normalizeHostValue(value) {
66
+ const trimmed = value.trim();
67
+ if (!trimmed) return trimmed;
68
+ try {
69
+ if (trimmed.includes("://")) {
70
+ return new URL(trimmed).host;
71
+ }
72
+ } catch {
34
73
  }
35
- const host = url.hostname;
36
- const model = url.pathname.replace(/^\//, "");
37
- const label = url.username || void 0;
38
- const apiKey = url.password || void 0;
39
- const params = {};
40
- for (const [key, value] of url.searchParams) {
41
- params[key] = value;
74
+ return trimmed.replace(/^\/\//, "").split("/")[0] ?? trimmed;
75
+ }
76
+ function envHostOverride(alias, env) {
77
+ const upper = alias.toUpperCase();
78
+ const override = env[`LLM_STRINGS_${upper}_HOST`] ?? env[`LLM_STRINGS_HOST_${upper}`];
79
+ return override?.trim() ? override : void 0;
80
+ }
81
+ function resolveHostAlias(host, env = readProcessEnv()) {
82
+ const normalizedHost = host.toLowerCase();
83
+ if (!hasOwn(HOST_ALIASES, normalizedHost)) {
84
+ return { host };
42
85
  }
86
+ const alias = normalizedHost;
87
+ const override = envHostOverride(alias, env);
43
88
  return {
44
- raw: connectionString,
45
- host,
46
- model,
47
- label,
48
- apiKey,
49
- params
89
+ host: normalizeHostValue(override ?? HOST_ALIASES[alias]),
90
+ alias
50
91
  };
51
92
  }
52
-
53
- // src/provider-core.ts
93
+ function providerFromHostAlias(alias) {
94
+ const normalizedAlias = alias.toLowerCase();
95
+ if (hasOwn(PROVIDER_PARAMS, normalizedAlias)) {
96
+ return normalizedAlias;
97
+ }
98
+ return void 0;
99
+ }
54
100
  function detectProvider(host) {
101
+ host = host.toLowerCase();
55
102
  if (host.includes("openrouter")) return "openrouter";
56
103
  if (host.includes("gateway.ai.vercel")) return "vercel";
57
104
  if (host.includes("amazonaws") || host.includes("bedrock")) return "bedrock";
@@ -214,11 +261,40 @@ var PROVIDER_PARAMS = {
214
261
  };
215
262
  var PARAM_SPECS = {
216
263
  openai: {
217
- temperature: { type: "number", min: 0, max: 2, default: 0.7, description: "Controls randomness" },
218
- max_tokens: { type: "number", min: 1, default: 4096, description: "Maximum output tokens" },
219
- top_p: { type: "number", min: 0, max: 1, default: 1, description: "Nucleus sampling" },
220
- frequency_penalty: { type: "number", min: -2, max: 2, default: 0, description: "Penalize frequent tokens" },
221
- presence_penalty: { type: "number", min: -2, max: 2, default: 0, description: "Penalize repeated topics" },
264
+ temperature: {
265
+ type: "number",
266
+ min: 0,
267
+ max: 2,
268
+ default: 0.7,
269
+ description: "Controls randomness"
270
+ },
271
+ max_tokens: {
272
+ type: "number",
273
+ min: 1,
274
+ default: 4096,
275
+ description: "Maximum output tokens"
276
+ },
277
+ top_p: {
278
+ type: "number",
279
+ min: 0,
280
+ max: 1,
281
+ default: 1,
282
+ description: "Nucleus sampling"
283
+ },
284
+ frequency_penalty: {
285
+ type: "number",
286
+ min: -2,
287
+ max: 2,
288
+ default: 0,
289
+ description: "Penalize frequent tokens"
290
+ },
291
+ presence_penalty: {
292
+ type: "number",
293
+ min: -2,
294
+ max: 2,
295
+ default: 0,
296
+ description: "Penalize repeated topics"
297
+ },
222
298
  stop: { type: "string", description: "Stop sequences" },
223
299
  n: { type: "number", min: 1, default: 1, description: "Completions count" },
224
300
  seed: { type: "number", description: "Random seed" },
@@ -231,73 +307,288 @@ var PARAM_SPECS = {
231
307
  }
232
308
  },
233
309
  anthropic: {
234
- temperature: { type: "number", min: 0, max: 1, default: 0.7, description: "Controls randomness" },
235
- max_tokens: { type: "number", min: 1, default: 4096, description: "Maximum output tokens" },
236
- top_p: { type: "number", min: 0, max: 1, default: 1, description: "Nucleus sampling" },
237
- top_k: { type: "number", min: 0, default: 40, description: "Top-K sampling" },
310
+ temperature: {
311
+ type: "number",
312
+ min: 0,
313
+ max: 1,
314
+ default: 0.7,
315
+ description: "Controls randomness"
316
+ },
317
+ max_tokens: {
318
+ type: "number",
319
+ min: 1,
320
+ default: 4096,
321
+ description: "Maximum output tokens"
322
+ },
323
+ top_p: {
324
+ type: "number",
325
+ min: 0,
326
+ max: 1,
327
+ default: 1,
328
+ description: "Nucleus sampling"
329
+ },
330
+ top_k: {
331
+ type: "number",
332
+ min: 0,
333
+ default: 40,
334
+ description: "Top-K sampling"
335
+ },
238
336
  stop_sequences: { type: "string", description: "Stop sequences" },
239
337
  stream: { type: "boolean", default: false, description: "Stream response" },
240
- effort: { type: "string", values: ["low", "medium", "high", "max"], default: "medium", description: "Thinking effort" },
241
- cache_control: { type: "string", values: ["ephemeral"], default: "ephemeral", description: "Cache control" },
242
- cache_ttl: { type: "string", values: ["5m", "1h"], default: "5m", description: "Cache TTL" }
338
+ effort: {
339
+ type: "string",
340
+ values: ["low", "medium", "high", "max"],
341
+ default: "medium",
342
+ description: "Thinking effort"
343
+ },
344
+ cache_control: {
345
+ type: "string",
346
+ values: ["ephemeral"],
347
+ default: "ephemeral",
348
+ description: "Cache control"
349
+ },
350
+ cache_ttl: {
351
+ type: "string",
352
+ values: ["5m", "1h"],
353
+ default: "5m",
354
+ description: "Cache TTL"
355
+ }
243
356
  },
244
357
  google: {
245
- temperature: { type: "number", min: 0, max: 2, default: 0.7, description: "Controls randomness" },
246
- maxOutputTokens: { type: "number", min: 1, default: 4096, description: "Maximum output tokens" },
247
- topP: { type: "number", min: 0, max: 1, default: 1, description: "Nucleus sampling" },
248
- topK: { type: "number", min: 0, default: 40, description: "Top-K sampling" },
249
- frequencyPenalty: { type: "number", min: -2, max: 2, default: 0, description: "Penalize frequent tokens" },
250
- presencePenalty: { type: "number", min: -2, max: 2, default: 0, description: "Penalize repeated topics" },
358
+ temperature: {
359
+ type: "number",
360
+ min: 0,
361
+ max: 2,
362
+ default: 0.7,
363
+ description: "Controls randomness"
364
+ },
365
+ maxOutputTokens: {
366
+ type: "number",
367
+ min: 1,
368
+ default: 4096,
369
+ description: "Maximum output tokens"
370
+ },
371
+ topP: {
372
+ type: "number",
373
+ min: 0,
374
+ max: 1,
375
+ default: 1,
376
+ description: "Nucleus sampling"
377
+ },
378
+ topK: {
379
+ type: "number",
380
+ min: 0,
381
+ default: 40,
382
+ description: "Top-K sampling"
383
+ },
384
+ frequencyPenalty: {
385
+ type: "number",
386
+ min: -2,
387
+ max: 2,
388
+ default: 0,
389
+ description: "Penalize frequent tokens"
390
+ },
391
+ presencePenalty: {
392
+ type: "number",
393
+ min: -2,
394
+ max: 2,
395
+ default: 0,
396
+ description: "Penalize repeated topics"
397
+ },
251
398
  stopSequences: { type: "string", description: "Stop sequences" },
252
- candidateCount: { type: "number", min: 1, default: 1, description: "Candidate count" },
399
+ candidateCount: {
400
+ type: "number",
401
+ min: 1,
402
+ default: 1,
403
+ description: "Candidate count"
404
+ },
253
405
  stream: { type: "boolean", default: false, description: "Stream response" },
254
406
  seed: { type: "number", description: "Random seed" },
255
407
  responseMimeType: { type: "string", description: "Response MIME type" },
256
408
  responseSchema: { type: "string", description: "Response schema" }
257
409
  },
258
410
  mistral: {
259
- temperature: { type: "number", min: 0, max: 1, default: 0.7, description: "Controls randomness" },
260
- max_tokens: { type: "number", min: 1, default: 4096, description: "Maximum output tokens" },
261
- top_p: { type: "number", min: 0, max: 1, default: 1, description: "Nucleus sampling" },
262
- frequency_penalty: { type: "number", min: -2, max: 2, default: 0, description: "Penalize frequent tokens" },
263
- presence_penalty: { type: "number", min: -2, max: 2, default: 0, description: "Penalize repeated topics" },
411
+ temperature: {
412
+ type: "number",
413
+ min: 0,
414
+ max: 1,
415
+ default: 0.7,
416
+ description: "Controls randomness"
417
+ },
418
+ max_tokens: {
419
+ type: "number",
420
+ min: 1,
421
+ default: 4096,
422
+ description: "Maximum output tokens"
423
+ },
424
+ top_p: {
425
+ type: "number",
426
+ min: 0,
427
+ max: 1,
428
+ default: 1,
429
+ description: "Nucleus sampling"
430
+ },
431
+ frequency_penalty: {
432
+ type: "number",
433
+ min: -2,
434
+ max: 2,
435
+ default: 0,
436
+ description: "Penalize frequent tokens"
437
+ },
438
+ presence_penalty: {
439
+ type: "number",
440
+ min: -2,
441
+ max: 2,
442
+ default: 0,
443
+ description: "Penalize repeated topics"
444
+ },
264
445
  stop: { type: "string", description: "Stop sequences" },
265
446
  n: { type: "number", min: 1, default: 1, description: "Completions count" },
266
447
  random_seed: { type: "number", description: "Random seed" },
267
448
  stream: { type: "boolean", default: false, description: "Stream response" },
268
- safe_prompt: { type: "boolean", default: false, description: "Enable safe prompt" },
269
- min_tokens: { type: "number", min: 0, default: 0, description: "Minimum tokens" }
449
+ safe_prompt: {
450
+ type: "boolean",
451
+ default: false,
452
+ description: "Enable safe prompt"
453
+ },
454
+ min_tokens: {
455
+ type: "number",
456
+ min: 0,
457
+ default: 0,
458
+ description: "Minimum tokens"
459
+ }
270
460
  },
271
461
  cohere: {
272
- temperature: { type: "number", min: 0, max: 1, default: 0.7, description: "Controls randomness" },
273
- max_tokens: { type: "number", min: 1, default: 4096, description: "Maximum output tokens" },
274
- p: { type: "number", min: 0, max: 1, default: 1, description: "Nucleus sampling (p)" },
275
- k: { type: "number", min: 0, max: 500, default: 40, description: "Top-K sampling (k)" },
276
- frequency_penalty: { type: "number", min: 0, max: 1, default: 0, description: "Penalize frequent tokens" },
277
- presence_penalty: { type: "number", min: 0, max: 1, default: 0, description: "Penalize repeated topics" },
462
+ temperature: {
463
+ type: "number",
464
+ min: 0,
465
+ max: 1,
466
+ default: 0.7,
467
+ description: "Controls randomness"
468
+ },
469
+ max_tokens: {
470
+ type: "number",
471
+ min: 1,
472
+ default: 4096,
473
+ description: "Maximum output tokens"
474
+ },
475
+ p: {
476
+ type: "number",
477
+ min: 0,
478
+ max: 1,
479
+ default: 1,
480
+ description: "Nucleus sampling (p)"
481
+ },
482
+ k: {
483
+ type: "number",
484
+ min: 0,
485
+ max: 500,
486
+ default: 40,
487
+ description: "Top-K sampling (k)"
488
+ },
489
+ frequency_penalty: {
490
+ type: "number",
491
+ min: 0,
492
+ max: 1,
493
+ default: 0,
494
+ description: "Penalize frequent tokens"
495
+ },
496
+ presence_penalty: {
497
+ type: "number",
498
+ min: 0,
499
+ max: 1,
500
+ default: 0,
501
+ description: "Penalize repeated topics"
502
+ },
278
503
  stop_sequences: { type: "string", description: "Stop sequences" },
279
504
  stream: { type: "boolean", default: false, description: "Stream response" },
280
505
  seed: { type: "number", description: "Random seed" }
281
506
  },
282
507
  bedrock: {
283
508
  // Converse API inferenceConfig params
284
- temperature: { type: "number", min: 0, max: 1, default: 0.7, description: "Controls randomness" },
285
- maxTokens: { type: "number", min: 1, default: 4096, description: "Maximum output tokens" },
286
- topP: { type: "number", min: 0, max: 1, default: 1, description: "Nucleus sampling" },
287
- topK: { type: "number", min: 0, default: 40, description: "Top-K sampling" },
509
+ temperature: {
510
+ type: "number",
511
+ min: 0,
512
+ max: 1,
513
+ default: 0.7,
514
+ description: "Controls randomness"
515
+ },
516
+ maxTokens: {
517
+ type: "number",
518
+ min: 1,
519
+ default: 4096,
520
+ description: "Maximum output tokens"
521
+ },
522
+ topP: {
523
+ type: "number",
524
+ min: 0,
525
+ max: 1,
526
+ default: 1,
527
+ description: "Nucleus sampling"
528
+ },
529
+ topK: {
530
+ type: "number",
531
+ min: 0,
532
+ default: 40,
533
+ description: "Top-K sampling"
534
+ },
288
535
  stopSequences: { type: "string", description: "Stop sequences" },
289
536
  stream: { type: "boolean", default: false, description: "Stream response" },
290
- cache_control: { type: "string", values: ["ephemeral"], default: "ephemeral", description: "Cache control" },
291
- cache_ttl: { type: "string", values: ["5m", "1h"], default: "5m", description: "Cache TTL" }
537
+ cache_control: {
538
+ type: "string",
539
+ values: ["ephemeral"],
540
+ default: "ephemeral",
541
+ description: "Cache control"
542
+ },
543
+ cache_ttl: {
544
+ type: "string",
545
+ values: ["5m", "1h"],
546
+ default: "5m",
547
+ description: "Cache TTL"
548
+ }
292
549
  },
293
550
  openrouter: {
294
551
  // Loose validation — proxies to many providers with varying ranges
295
- temperature: { type: "number", min: 0, max: 2, default: 0.7, description: "Controls randomness" },
296
- max_tokens: { type: "number", min: 1, default: 4096, description: "Maximum output tokens" },
297
- top_p: { type: "number", min: 0, max: 1, default: 1, description: "Nucleus sampling" },
298
- top_k: { type: "number", min: 0, default: 40, description: "Top-K sampling" },
299
- frequency_penalty: { type: "number", min: -2, max: 2, default: 0, description: "Penalize frequent tokens" },
300
- presence_penalty: { type: "number", min: -2, max: 2, default: 0, description: "Penalize repeated topics" },
552
+ temperature: {
553
+ type: "number",
554
+ min: 0,
555
+ max: 2,
556
+ default: 0.7,
557
+ description: "Controls randomness"
558
+ },
559
+ max_tokens: {
560
+ type: "number",
561
+ min: 1,
562
+ default: 4096,
563
+ description: "Maximum output tokens"
564
+ },
565
+ top_p: {
566
+ type: "number",
567
+ min: 0,
568
+ max: 1,
569
+ default: 1,
570
+ description: "Nucleus sampling"
571
+ },
572
+ top_k: {
573
+ type: "number",
574
+ min: 0,
575
+ default: 40,
576
+ description: "Top-K sampling"
577
+ },
578
+ frequency_penalty: {
579
+ type: "number",
580
+ min: -2,
581
+ max: 2,
582
+ default: 0,
583
+ description: "Penalize frequent tokens"
584
+ },
585
+ presence_penalty: {
586
+ type: "number",
587
+ min: -2,
588
+ max: 2,
589
+ default: 0,
590
+ description: "Penalize repeated topics"
591
+ },
301
592
  stop: { type: "string", description: "Stop sequences" },
302
593
  n: { type: "number", min: 1, default: 1, description: "Completions count" },
303
594
  seed: { type: "number", description: "Random seed" },
@@ -311,12 +602,46 @@ var PARAM_SPECS = {
311
602
  },
312
603
  vercel: {
313
604
  // Loose validation — proxies to many providers with varying ranges
314
- temperature: { type: "number", min: 0, max: 2, default: 0.7, description: "Controls randomness" },
315
- max_tokens: { type: "number", min: 1, default: 4096, description: "Maximum output tokens" },
316
- top_p: { type: "number", min: 0, max: 1, default: 1, description: "Nucleus sampling" },
317
- top_k: { type: "number", min: 0, default: 40, description: "Top-K sampling" },
318
- frequency_penalty: { type: "number", min: -2, max: 2, default: 0, description: "Penalize frequent tokens" },
319
- presence_penalty: { type: "number", min: -2, max: 2, default: 0, description: "Penalize repeated topics" },
605
+ temperature: {
606
+ type: "number",
607
+ min: 0,
608
+ max: 2,
609
+ default: 0.7,
610
+ description: "Controls randomness"
611
+ },
612
+ max_tokens: {
613
+ type: "number",
614
+ min: 1,
615
+ default: 4096,
616
+ description: "Maximum output tokens"
617
+ },
618
+ top_p: {
619
+ type: "number",
620
+ min: 0,
621
+ max: 1,
622
+ default: 1,
623
+ description: "Nucleus sampling"
624
+ },
625
+ top_k: {
626
+ type: "number",
627
+ min: 0,
628
+ default: 40,
629
+ description: "Top-K sampling"
630
+ },
631
+ frequency_penalty: {
632
+ type: "number",
633
+ min: -2,
634
+ max: 2,
635
+ default: 0,
636
+ description: "Penalize frequent tokens"
637
+ },
638
+ presence_penalty: {
639
+ type: "number",
640
+ min: -2,
641
+ max: 2,
642
+ default: 0,
643
+ description: "Penalize repeated topics"
644
+ },
320
645
  stop: { type: "string", description: "Stop sequences" },
321
646
  n: { type: "number", min: 1, default: 1, description: "Completions count" },
322
647
  seed: { type: "number", description: "Random seed" },
@@ -343,7 +668,13 @@ function detectGatewaySubProvider(model) {
343
668
  const slash = model.indexOf("/");
344
669
  if (slash < 1) return void 0;
345
670
  const prefix = model.slice(0, slash);
346
- const direct = ["openai", "anthropic", "google", "mistral", "cohere"];
671
+ const direct = [
672
+ "openai",
673
+ "anthropic",
674
+ "google",
675
+ "mistral",
676
+ "cohere"
677
+ ];
347
678
  return direct.find((p) => p === prefix);
348
679
  }
349
680
  var REASONING_MODEL_UNSUPPORTED = /* @__PURE__ */ new Set([
@@ -403,9 +734,36 @@ var CACHE_TTLS = {
403
734
  };
404
735
  var DURATION_RE = /^\d+[mh]$/;
405
736
 
737
+ // src/parse.ts
738
+ function parse(connectionString) {
739
+ const url = new URL(connectionString);
740
+ if (url.protocol !== "llm:") {
741
+ throw new Error(
742
+ `Invalid scheme: expected "llm://", got "${url.protocol}//"`
743
+ );
744
+ }
745
+ const { host, alias: hostAlias } = resolveHostAlias(url.host);
746
+ const model = url.pathname.replace(/^\//, "");
747
+ const label = url.username || void 0;
748
+ const apiKey = url.password || void 0;
749
+ const params = {};
750
+ for (const [key, value] of url.searchParams) {
751
+ params[key] = value;
752
+ }
753
+ return {
754
+ raw: connectionString,
755
+ host,
756
+ hostAlias,
757
+ model,
758
+ label,
759
+ apiKey,
760
+ params
761
+ };
762
+ }
763
+
406
764
  // src/normalize.ts
407
765
  function normalize(config, options = {}) {
408
- const provider = detectProvider(config.host);
766
+ const provider = (config.hostAlias ? providerFromHostAlias(config.hostAlias) : void 0) ?? detectProvider(config.host);
409
767
  const subProvider = provider && isGatewayProvider(provider) ? detectGatewaySubProvider(config.model) : void 0;
410
768
  const changes = [];
411
769
  const params = {};
@@ -590,11 +948,7 @@ function validate(connectionString, options = {}) {
590
948
  }
591
949
  let spec = specs[key];
592
950
  if (subProvider && gatewayReverseMap && !spec) {
593
- const result = lookupSubProviderSpec(
594
- key,
595
- gatewayReverseMap,
596
- subProvider
597
- );
951
+ const result = lookupSubProviderSpec(key, gatewayReverseMap, subProvider);
598
952
  spec = result.spec;
599
953
  }
600
954
  if (!spec) continue;