llm-strings 1.3.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.
Files changed (49) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +339 -339
  3. package/dist/ai-sdk.cjs +1146 -117
  4. package/dist/ai-sdk.d.cts +1 -1
  5. package/dist/ai-sdk.d.ts +1 -1
  6. package/dist/ai-sdk.js +95 -58
  7. package/dist/{chunk-7HE4RH6X.js → chunk-4BE457QA.js} +16 -29
  8. package/dist/{chunk-TQJ2ABCT.js → chunk-6HQOCOQI.js} +1 -2
  9. package/dist/{chunk-NZR5DUX5.js → chunk-7Z7DCLZN.js} +14 -3
  10. package/dist/{chunk-OCJX4QFJ.js → chunk-PCJDQTOV.js} +520 -76
  11. package/dist/index.cjs +542 -95
  12. package/dist/index.d.cts +1 -1
  13. package/dist/index.d.ts +1 -1
  14. package/dist/index.js +4 -5
  15. package/dist/normalize.cjs +1065 -63
  16. package/dist/normalize.d.cts +1 -1
  17. package/dist/normalize.d.ts +1 -1
  18. package/dist/normalize.js +2 -3
  19. package/dist/parse.cjs +1075 -1
  20. package/dist/parse.d.cts +4 -4
  21. package/dist/parse.d.ts +4 -4
  22. package/dist/parse.js +2 -3
  23. package/dist/{provider-core-B934MuhJ.d.cts → provider-core-B1GMszQP.d.cts} +2 -3
  24. package/dist/{provider-core-B934MuhJ.d.ts → provider-core-B1GMszQP.d.ts} +2 -3
  25. package/dist/providers.cjs +542 -697
  26. package/dist/providers.d.cts +3 -8
  27. package/dist/providers.d.ts +3 -8
  28. package/dist/providers.js +25 -624
  29. package/dist/validate.cjs +542 -95
  30. package/dist/validate.d.cts +1 -1
  31. package/dist/validate.d.ts +1 -1
  32. package/dist/validate.js +4 -5
  33. package/package.json +16 -2
  34. package/dist/ai-sdk.cjs.map +0 -1
  35. package/dist/ai-sdk.js.map +0 -1
  36. package/dist/chunk-7HE4RH6X.js.map +0 -1
  37. package/dist/chunk-NZR5DUX5.js.map +0 -1
  38. package/dist/chunk-OCJX4QFJ.js.map +0 -1
  39. package/dist/chunk-TQJ2ABCT.js.map +0 -1
  40. package/dist/index.cjs.map +0 -1
  41. package/dist/index.js.map +0 -1
  42. package/dist/normalize.cjs.map +0 -1
  43. package/dist/normalize.js.map +0 -1
  44. package/dist/parse.cjs.map +0 -1
  45. package/dist/parse.js.map +0 -1
  46. package/dist/providers.cjs.map +0 -1
  47. package/dist/providers.js.map +0 -1
  48. package/dist/validate.cjs.map +0 -1
  49. package/dist/validate.js.map +0 -1
package/README.md CHANGED
@@ -8,25 +8,47 @@
8
8
  [![License](https://img.shields.io/npm/l/llm-strings.svg)](https://github.com/justsml/llm-strings/blob/main/LICENSE)
9
9
  [![TypeScript](https://img.shields.io/badge/TypeScript-5.5-blue.svg)](https://www.typescriptlang.org/)
10
10
  [![Zero Dependencies](https://img.shields.io/badge/dependencies-0-brightgreen.svg)](https://www.npmjs.com/package/llm-strings)
11
+ [![Node](https://img.shields.io/badge/node-%3E%3D20-339933.svg)](https://nodejs.org/)
12
+
13
+ **Parse, normalize, validate, and build portable `llm://` URLs across AI providers.**
14
+
15
+ [Install](#install) · [Quick Start](#quick-start) · [Examples](#examples) · [Supported Providers](#supported-providers) · [API](#api-reference)
11
16
 
12
17
  </div>
13
18
 
14
19
  ---
15
20
 
16
- ![the parts of a LLM connection string](./assets/inline-url-diagram-dark.svg)
21
+ ![The parts of an LLM connection string](./assets/inline-url-diagram-dark.svg)
17
22
 
18
23
  ```ini
19
- llm://api.openai.com/gpt-5.2?temp=0.7&max=2000
20
- llm://my-app:sk-key-123@api.anthropic.com/claude-sonnet-4-5?cache=5m
21
- llm://bedrock-runtime.us-east-1.amazonaws.com/anthropic.claude-sonnet-4-5-20250929-v1:0?temp=0.5
22
- llm://openai/gpt-5.2?temp=0.7&max=2000
24
+ llm://openai/gpt-5.5?effort=medium&maxTokens=2000
25
+ llm://anthropic/claude-opus-4-8?cache=5m&effort=max
26
+ llm://bedrock/anthropic.claude-sonnet-4-5-20250929-v1:0?temp=0.5&max=4096
27
+ llm://openrouter/anthropic/claude-sonnet-4-5?temp=0.7&max=2000
23
28
  ```
24
29
 
25
- Every LLM provider invented their own parameter names. `max_tokens` vs `maxOutputTokens` vs `maxTokens`. `top_p` vs `topP` vs `p`. `stop` vs `stop_sequences` vs `stopSequences`. You write the config once, then rewrite it for every provider.
30
+ Every LLM provider invented slightly different names for the same knobs:
31
+ `max_tokens` vs `maxOutputTokens` vs `maxTokens`, `top_p` vs `topP` vs `p`,
32
+ `stop` vs `stop_sequences` vs `stopSequences`.
33
+
34
+ **llm-strings** gives you one portable format for model configuration. Put the
35
+ whole config in an env var, normalize it to the provider's API shape, validate
36
+ it before you spend tokens, and build UI controls from the same metadata.
26
37
 
27
- **llm-strings** gives you a single, portable format. Parse it, normalize it to any provider's API, and validate it — all in one library with zero dependencies.
38
+ Based on the [LLM Connection Strings](https://danlevy.net/llm-connection-strings/)
39
+ proposal by Dan Levy. See the [draft IETF RFC for `llm://`](https://datatracker.ietf.org/doc/html/draft-levy-llm-uri-scheme-00).
28
40
 
29
- Based on the [LLM Connection Strings](https://danlevy.net/llm-connection-strings/) article by Dan Levy. See [draft IETF RFC for `llm://`](https://datatracker.ietf.org/doc/html/draft-levy-llm-uri-scheme-00).
41
+ ## Why developers use it
42
+
43
+ - **One config string** for host, model, credentials, and generation params.
44
+ - **Provider-native output** from provider-agnostic input like `temp=0.7&max=2000`.
45
+ - **Early validation** for ranges, mutual exclusions, Bedrock model-family
46
+ rules, and OpenAI reasoning-family normalization.
47
+ - **Short aliases** like `openai`, `anthropic`, `google`, `bedrock`, `groq`, and
48
+ `openrouter`, with env overrides for private or regional endpoints.
49
+ - **AI SDK providerOptions** generation for provider-specific settings.
50
+ - **Zero runtime dependencies**, ESM + CJS, full TypeScript declarations, and
51
+ sub-path imports for smaller bundles.
30
52
 
31
53
  ## Install
32
54
 
@@ -34,164 +56,87 @@ Based on the [LLM Connection Strings](https://danlevy.net/llm-connection-strings
34
56
  npm install llm-strings
35
57
  ```
36
58
 
37
- ## Quick Start
38
-
39
- ```ts
40
- import { parse, normalize, validate, build } from "llm-strings";
41
-
42
- // Parse a connection string into structured config
43
- const config = parse("llm://api.openai.com/gpt-5.2?temp=0.7&max=2000");
44
- // → { host: "api.openai.com", model: "gpt-5.2", params: { temp: "0.7", max: "2000" } }
45
-
46
- // Normalize aliases and map to the provider's actual API param names
47
- const { config: normalized, provider } = normalize(config);
48
- // → params: { temperature: "0.7", max_tokens: "2000" }, provider: "openai"
49
-
50
- // Validate against provider specs (returns [] if valid)
51
- const issues = validate("llm://api.openai.com/gpt-5.2?temp=3.0");
52
- // → [{ param: "temperature", message: '"temperature" must be <= 2, got 3', severity: "error" }]
53
-
54
- // Build a connection string from a config object
55
- const str = build({
56
- host: "api.openai.com",
57
- model: "gpt-5.2",
58
- params: { temperature: "0.7" },
59
- });
60
- // → "llm://api.openai.com/gpt-5.2?temperature=0.7"
61
- ```
62
-
63
- ## Why Connection Strings?
64
-
65
- You already use them for databases: `postgres://user:pass@host/db`. They're compact, portable, and easy to pass through environment variables. LLM configs deserve the same treatment.
66
-
67
- **Store your entire model config in one env var:**
68
-
69
59
  ```bash
70
- LLM_URL="llm://my-app:sk-proj-abc123@api.openai.com/gpt-5.2?temp=0.7&max=2000"
60
+ pnpm add llm-strings
71
61
  ```
72
62
 
73
- **Switch providers by changing a string, not refactoring code:**
74
-
75
63
  ```bash
76
- # Monday: OpenAI
77
- LLM_URL="llm://api.openai.com/gpt-5.2?temp=0.7&max=2000"
78
-
79
- # Tuesday: Anthropic
80
- LLM_URL="llm://api.anthropic.com/claude-sonnet-4-5?temp=0.7&max=2000"
81
-
82
- # Wednesday: Bedrock in production
83
- LLM_URL="llm://bedrock-runtime.us-east-1.amazonaws.com/anthropic.claude-sonnet-4-5-20250929-v1:0?temp=0.7&max=2000"
64
+ yarn add llm-strings
84
65
  ```
85
66
 
86
- Your code stays the same. `normalize()` handles the parameter translation.
87
-
88
- ## Benefits
89
-
90
- - **One format, every provider** — Write `temp=0.7&max=2000` once. Normalization maps it to `temperature`, `max_tokens`, `maxOutputTokens`, `maxTokens`, or whatever your provider calls it.
91
- - **Catch mistakes early** — `validate()` checks types, ranges, and provider-specific rules before you burn tokens on a bad request.
92
- - **Zero dependencies** — Pure TypeScript. No runtime baggage.
93
- - **Portable config** — Fits in an env var, a CLI flag, a config file, or a database column.
94
- - **Shorthand aliases** — Use `temp`, `max`, `topp`, `freq`, `pres` — they all expand to the right thing.
95
- - **Short host aliases** — Use `llm://openai/...`, `llm://anthropic/...`, `llm://bedrock/...`, etc. Env overrides can redirect aliases to regional or private endpoints.
96
-
97
- ## Format
98
-
99
- ```ini
100
- llm://[label[:apiKey]@]host/model[?params]
67
+ ```bash
68
+ bun add llm-strings
101
69
  ```
102
70
 
103
- | Part | Required | Description | Example |
104
- | -------- | -------- | ---------------------------------- | -------------------------- |
105
- | `label` | No | App name or identifier | `my-app` |
106
- | `apiKey` | No | API key (in the password position) | `sk-proj-abc123` |
107
- | `host` | Yes | Provider's API host or short alias | `api.openai.com`, `openai` |
108
- | `model` | Yes | Model name or ID | `gpt-5.2` |
109
- | `params` | No | Key-value config (query string) | `temp=0.7&max=2000` |
110
-
111
- ## Examples
112
-
113
- ### Switching between providers
114
-
115
- Write portable params, let `normalize()` translate them:
71
+ ## Quick Start
116
72
 
117
73
  ```ts
118
- import { parse, normalize } from "llm-strings";
119
-
120
- // Same logical config, different providers
121
- const strings = [
122
- "llm://api.openai.com/gpt-5.2?temp=0.7&max=2000&top_p=0.9",
123
- "llm://api.anthropic.com/claude-sonnet-4-5?temp=0.7&max=2000&top_p=0.9",
124
- "llm://generativelanguage.googleapis.com/gemini-3-flash-preview?temp=0.7&max=2000&top_p=0.9",
125
- ];
126
-
127
- for (const str of strings) {
128
- const { config, provider } = normalize(parse(str));
129
- console.log(`${provider}:`, config.params);
130
- }
131
- // openai: { temperature: "0.7", max_tokens: "2000", top_p: "0.9" }
132
- // anthropic: { temperature: "0.7", max_tokens: "2000", top_p: "0.9" }
133
- // google: { temperature: "0.7", maxOutputTokens: "2000", topP: "0.9" }
134
- ```
74
+ import { build, normalize, parse, validate } from "llm-strings";
135
75
 
136
- ### Short host aliases
76
+ const input = "llm://openai/gpt-4o?temp=0.7&max=2000&topp=0.9";
137
77
 
138
- Provider IDs can be used as short hostnames:
139
-
140
- ```ts
141
- import { parse, normalize } from "llm-strings";
78
+ const parsed = parse(input);
79
+ // {
80
+ // raw: "llm://openai/gpt-4o?temp=0.7&max=2000&topp=0.9",
81
+ // host: "api.openai.com",
82
+ // hostAlias: "openai",
83
+ // model: "gpt-4o",
84
+ // params: { temp: "0.7", max: "2000", topp: "0.9" }
85
+ // }
142
86
 
143
- const { config, provider } = normalize(parse("llm://openai/gpt-5.2?temp=0.7"));
87
+ const { config, provider } = normalize(parsed);
88
+ // provider: "openai"
89
+ // config.params: { temperature: "0.7", max_tokens: "2000", top_p: "0.9" }
90
+
91
+ const issues = validate("llm://anthropic/claude-sonnet-4-5?temp=0.7&top_p=0.9");
92
+ // [
93
+ // {
94
+ // param: "temperature",
95
+ // value: "0.7",
96
+ // severity: "error",
97
+ // message: "Cannot specify both \"temperature\" and \"top_p\" for Anthropic models."
98
+ // }
99
+ // ]
144
100
 
145
- config.host; // "api.openai.com"
146
- provider; // "openai"
101
+ const url = build({
102
+ host: "anthropic",
103
+ model: "claude-sonnet-4-5",
104
+ params: { temp: "0.7", max: "4096" },
105
+ });
106
+ // "llm://api.anthropic.com/claude-sonnet-4-5?temp=0.7&max=4096"
147
107
  ```
148
108
 
149
- Built-in aliases: `openai`, `anthropic`, `google`, `mistral`, `cohere`, `bedrock`, `openrouter`, `vercel`.
150
-
151
- Set env overrides to point an alias at a regional or private endpoint:
109
+ ## Format
152
110
 
153
- ```sh
154
- LLM_STRINGS_OPENAI_HOST="regional.openai.example.com"
155
- LLM_STRINGS_BEDROCK_HOST="bedrock-runtime.us-west-2.amazonaws.com"
111
+ ```ini
112
+ llm://[label[:apiKey]@]host/model[?params]
156
113
  ```
157
114
 
158
- The alternate form `LLM_STRINGS_HOST_OPENAI` is also supported. Overrides may include a scheme or path; only the host portion is used.
159
-
160
- ### Validating before calling the API
115
+ | Part | Required | Description | Example |
116
+ | -------- | -------- | --------------------------------- | -------------------------- |
117
+ | `label` | No | App name or environment label | `worker` |
118
+ | `apiKey` | No | API key in the password position | `sk-proj-abc123` |
119
+ | `host` | Yes | Provider host or short alias | `api.openai.com`, `openai` |
120
+ | `model` | Yes | Model name, route, or provider ID | `gpt-5.5` |
121
+ | `params` | No | Query-string generation settings | `effort=medium&max=2000` |
161
122
 
162
- Catch bad config before it hits the network:
163
-
164
- ```ts
165
- import { validate } from "llm-strings";
123
+ Connection strings can include secrets, so treat values containing `apiKey` like
124
+ credentials: store them in secret managers or env vars, and avoid logging them.
166
125
 
167
- // Anthropic doesn't allow temperature + top_p together
168
- const issues = validate(
169
- "llm://api.anthropic.com/claude-sonnet-4-5?temp=0.7&top_p=0.9",
170
- );
126
+ ## Examples
171
127
 
172
- for (const issue of issues) {
173
- console.error(`[${issue.severity}] ${issue.param}: ${issue.message}`);
174
- }
175
- // [error] temperature: Cannot specify both "temperature" and "top_p" for Anthropic models.
176
- ```
128
+ ### One env var for your model config
177
129
 
178
- ```ts
179
- // OpenAI reasoning models have different rules
180
- const issues = validate("llm://api.openai.com/o3?temp=0.7&max=2000");
181
- // [error] temperature: "temperature" is not supported by OpenAI reasoning model "o3".
182
- // Use "reasoning_effort" instead of temperature for controlling output.
130
+ ```bash
131
+ LLM_URL="llm://worker:sk-proj-abc123@openai/gpt-4o?temp=0.7&max=2000"
183
132
  ```
184
133
 
185
- ### Environment-driven config
186
-
187
134
  ```ts
188
- import { parse, normalize } from "llm-strings";
135
+ import { normalize, parse } from "llm-strings";
189
136
 
190
- // One env var holds the entire config
191
137
  const { config, provider } = normalize(parse(process.env.LLM_URL!));
192
138
 
193
- // Use the normalized params directly in your API call
194
- const response = await fetch(`https://${config.host}/v1/chat/completions`, {
139
+ await fetch(`https://${config.host}/v1/chat/completions`, {
195
140
  method: "POST",
196
141
  headers: {
197
142
  Authorization: `Bearer ${config.apiKey}`,
@@ -201,156 +146,215 @@ const response = await fetch(`https://${config.host}/v1/chat/completions`, {
201
146
  model: config.model,
202
147
  messages: [{ role: "user", content: "Hello!" }],
203
148
  ...Object.fromEntries(
204
- Object.entries(config.params).map(([k, v]) => [k, isNaN(+v) ? v : +v]),
149
+ Object.entries(config.params).map(([key, value]) => [
150
+ key,
151
+ Number.isNaN(Number(value)) ? value : Number(value),
152
+ ]),
205
153
  ),
206
154
  }),
207
155
  });
156
+
157
+ console.log(provider); // "openai"
208
158
  ```
209
159
 
210
- ### AI SDK providerOptions
160
+ ### Switch providers without changing app code
211
161
 
212
- The AI SDK adapter is available as a separate subpath so you can load it only
213
- where you need it:
162
+ ```bash
163
+ # OpenAI
164
+ LLM_URL="llm://openai/gpt-4o?temp=0.7&max=2000"
214
165
 
215
- ```ts
216
- const { createAiSdkProviderOptions } = await import("llm-strings/ai-sdk");
166
+ # Anthropic
167
+ LLM_URL="llm://anthropic/claude-sonnet-4-5?temp=0.7&max=2000"
217
168
 
218
- const { providerOptions } = createAiSdkProviderOptions(
219
- "llm://api.anthropic.com/claude-sonnet-4-5?cache=1h&effort=max",
220
- );
169
+ # Google
170
+ LLM_URL="llm://google/gemini-3.5-flash?temp=0.7&max=2000"
221
171
 
222
- // {
223
- // anthropic: {
224
- // cacheControl: { type: "ephemeral", ttl: "1h" },
225
- // effort: "max"
226
- // }
227
- // }
172
+ # Bedrock
173
+ LLM_URL="llm://bedrock/us.anthropic.claude-sonnet-4-5-20250929-v1:0?temp=0.7&max=2000"
228
174
  ```
229
175
 
230
- Common generation settings like `temperature`, `topP`, and `maxOutputTokens`
231
- belong on the AI SDK call itself, so this helper only emits provider-specific
232
- configuration such as Anthropic cache control, Bedrock cache points, OpenAI
233
- reasoning options, Mistral `safePrompt`, OpenRouter reasoning, and Vercel AI
234
- Gateway routing options.
176
+ ```ts
177
+ import { normalize, parse } from "llm-strings";
178
+
179
+ for (const value of [
180
+ "llm://openai/gpt-4o?temp=0.7&max=2000",
181
+ "llm://anthropic/claude-sonnet-4-5?temp=0.7&max=2000",
182
+ "llm://google/gemini-3.5-flash?temp=0.7&max=2000",
183
+ ]) {
184
+ const { config, provider } = normalize(parse(value));
185
+ console.log(provider, config.params);
186
+ }
235
187
 
236
- ### Prompt caching (Anthropic & Bedrock)
188
+ // openai { temperature: "0.7", max_tokens: "2000" }
189
+ // anthropic { temperature: "0.7", max_tokens: "2000" }
190
+ // google { temperature: "0.7", maxOutputTokens: "2000" }
191
+ ```
192
+
193
+ ### Resolve short host aliases
237
194
 
238
195
  ```ts
239
- import { parse, normalize } from "llm-strings";
196
+ import { normalize, parse } from "llm-strings";
240
197
 
241
- // cache=true cache_control=ephemeral
242
- const { config } = normalize(
243
- parse("llm://api.anthropic.com/claude-sonnet-4-5?max=4096&cache=true"),
198
+ const { config, provider } = normalize(
199
+ parse("llm://groq/llama-3.3-70b?max=1000"),
244
200
  );
245
- // → params: { max_tokens: "4096", cache_control: "ephemeral" }
246
201
 
247
- // cache=5m → cache_control=ephemeral + cache_ttl=5m
248
- const { config: withTtl } = normalize(
249
- parse("llm://api.anthropic.com/claude-sonnet-4-5?max=4096&cache=5m"),
250
- );
251
- // → params: { max_tokens: "4096", cache_control: "ephemeral", cache_ttl: "5m" }
202
+ config.host; // "api.groq.com"
203
+ provider; // "groq"
204
+ ```
252
205
 
253
- // Works on Bedrock too (Claude and Nova models)
254
- const { config: bedrock } = normalize(
255
- parse(
256
- "llm://bedrock-runtime.us-east-1.amazonaws.com/anthropic.claude-sonnet-4-5-20250929-v1:0?cache=1h",
257
- ),
258
- );
259
- // → params: { cache_control: "ephemeral", cache_ttl: "1h" }
206
+ Override any alias at deploy time:
207
+
208
+ ```bash
209
+ LLM_STRINGS_OPENAI_HOST="regional.openai.example.com"
210
+ LLM_STRINGS_BEDROCK_HOST="https://bedrock-runtime.us-west-2.amazonaws.com/model"
260
211
  ```
261
212
 
262
- ### Debugging normalization
213
+ The alternate form `LLM_STRINGS_HOST_OPENAI` is also supported. Overrides may
214
+ include a scheme or path; only the host portion is used.
263
215
 
264
- Use verbose mode to see exactly what was transformed:
216
+ ### Validate before calling the provider
265
217
 
266
218
  ```ts
267
- import { parse, normalize } from "llm-strings";
268
-
269
- const { changes } = normalize(
270
- parse(
271
- "llm://generativelanguage.googleapis.com/gemini-3-flash-preview?temp=0.7&max=2000&topp=0.9",
272
- ),
273
- { verbose: true },
274
- );
219
+ import { validate } from "llm-strings";
275
220
 
276
- for (const c of changes) {
277
- console.log(`${c.from} ${c.to} (${c.reason})`);
278
- }
279
- // temp → temperature (alias: "temp" → "temperature")
280
- // max → max_tokens (alias: "max" → "max_tokens")
281
- // max_tokens → maxOutputTokens (google uses "maxOutputTokens" instead of "max_tokens")
282
- // topp → top_p (alias: "topp" → "top_p")
283
- // top_p → topP (google uses "topP" instead of "top_p")
284
- ```
221
+ validate("llm://openai/gpt-4o?temp=3.0");
222
+ // [{ param: "temperature", message: "\"temperature\" must be <= 2, got 3", ... }]
285
223
 
286
- ### Building connection strings programmatically
224
+ validate("llm://openai/gpt-5.5?temp=0.7&max=2000");
225
+ // [] — temperature is a known unsupported reasoning-family param, so normalize() drops it
287
226
 
288
- ```ts
289
- import { build } from "llm-strings";
227
+ validate("llm://fal/fal-ai/flux-pro?future_model_param=1");
228
+ // [] unknown params pass through by default for model-specific schemas
290
229
 
291
- const url = build({
292
- host: "api.openai.com",
293
- model: "gpt-5.2",
294
- label: "my-app",
295
- apiKey: "sk-proj-abc123",
296
- params: { temperature: "0.7", max_tokens: "2000", stream: "true" },
297
- });
298
- // → "llm://my-app:sk-proj-abc123@api.openai.com/gpt-5.2?temperature=0.7&max_tokens=2000&stream=true"
230
+ validate("llm://fal/fal-ai/flux-pro?future_model_param=1", { strict: true });
231
+ // [{ param: "future_model_param", severity: "error", message: "Unknown param..." }]
299
232
  ```
300
233
 
301
- ### AWS Bedrock with cross-region inference
234
+ ### See exactly what changed
302
235
 
303
236
  ```ts
304
- import { parse, normalize } from "llm-strings";
305
- import { detectBedrockModelFamily } from "llm-strings/providers";
237
+ import { normalize, parse } from "llm-strings";
306
238
 
307
- const config = parse(
308
- "llm://bedrock-runtime.us-east-1.amazonaws.com/us.anthropic.claude-sonnet-4-5-20250929-v1:0?temp=0.5&max=4096",
239
+ const { changes } = normalize(
240
+ parse("llm://google/gemini-3.5-flash?temp=0.7&max=2000&topp=0.9"),
241
+ { verbose: true },
309
242
  );
310
243
 
311
- detectBedrockModelFamily(config.model);
312
- // "anthropic"
244
+ for (const change of changes) {
245
+ console.log(`${change.from} -> ${change.to} (${change.reason})`);
246
+ }
313
247
 
314
- const { config: normalized } = normalize(config);
315
- // params: { temperature: "0.5", maxTokens: "4096" }
316
- // (Bedrock Converse API uses camelCase)
248
+ // temp -> temperature (alias: "temp" -> "temperature")
249
+ // max -> max_tokens (alias: "max" -> "max_tokens")
250
+ // max_tokens -> maxOutputTokens (google uses "maxOutputTokens" instead of "max_tokens")
251
+ // topp -> top_p (alias: "topp" -> "top_p")
252
+ // top_p -> topP (google uses "topP" instead of "top_p")
317
253
  ```
318
254
 
319
- ### Gateway providers (OpenRouter, Vercel)
255
+ ### Build AI SDK providerOptions
256
+
257
+ The AI SDK adapter lives on a separate sub-path so you only load it when you
258
+ need it:
320
259
 
321
260
  ```ts
322
- import { parse, normalize, validate } from "llm-strings";
261
+ const { createAiSdkProviderOptions } = await import("llm-strings/ai-sdk");
323
262
 
324
- // OpenRouter proxies to any provider
325
- const { config } = normalize(
326
- parse("llm://openrouter.ai/anthropic/claude-sonnet-4-5?temp=0.7&max=2000"),
263
+ const { providerOptions } = createAiSdkProviderOptions(
264
+ "llm://anthropic/claude-sonnet-4-5?cache=1h&effort=max",
327
265
  );
328
- // → params: { temperature: "0.7", max_tokens: "2000" }
329
266
 
330
- // Reasoning model restrictions apply even through gateways
331
- const issues = validate("llm://openrouter.ai/openai/o3?temp=0.7");
332
- // → [{ param: "temperature", severity: "error",
333
- // message: "...not supported by OpenAI reasoning model..." }]
267
+ // {
268
+ // anthropic: {
269
+ // cacheControl: { type: "ephemeral", ttl: "1h" },
270
+ // effort: "max"
271
+ // }
272
+ // }
334
273
  ```
335
274
 
336
- ## Supported Providers
275
+ Common generation settings like temperature, top-p, and max output tokens
276
+ belong on the AI SDK call itself. The helper emits provider-specific options
277
+ such as Anthropic cache control, Bedrock cache points, OpenAI reasoning options,
278
+ Mistral `safePrompt`, OpenRouter routing, and Vercel AI Gateway routing,
279
+ including options such as `order`, `sort=ttft`, and `caching=auto`.
280
+
281
+ ### Build provider-aware UIs
282
+
283
+ ```ts
284
+ import { CANONICAL_PARAM_SPECS, PROVIDER_META } from "llm-strings/providers";
337
285
 
338
- | Provider | Host Pattern | Param Style |
339
- | ----------- | ---------------------------------------- | ----------- |
340
- | OpenAI | `api.openai.com` | snake_case |
341
- | Anthropic | `api.anthropic.com` | snake_case |
342
- | Google | `generativelanguage.googleapis.com` | camelCase |
343
- | Mistral | `api.mistral.ai` | snake_case |
344
- | Cohere | `api.cohere.com` | snake_case |
345
- | AWS Bedrock | `bedrock-runtime.{region}.amazonaws.com` | camelCase |
346
- | OpenRouter | `openrouter.ai` | snake_case |
347
- | Vercel AI | `gateway.ai.vercel.app` | snake_case |
286
+ PROVIDER_META.map(({ id, name, host, color }) => ({ id, name, host, color }));
287
+ // [{ id: "openai", name: "OpenAI", host: "api.openai.com", color: "#10a37f" }, ...]
348
288
 
349
- Gateways like OpenRouter and Vercel route to any upstream provider. Bedrock hosts models from multiple families (Anthropic, Meta, Amazon, Mistral, Cohere, AI21) with cross-region inference support. Each provider's parameter names differ — normalization handles the translation automatically.
289
+ CANONICAL_PARAM_SPECS.anthropic.temperature;
290
+ // {
291
+ // type: "number",
292
+ // min: 0,
293
+ // max: 1,
294
+ // default: 0.7,
295
+ // description: "Controls randomness"
296
+ // }
297
+ ```
350
298
 
351
- ## Shorthand Aliases
299
+ ## Supported Providers
352
300
 
353
- Use these shortcuts in your connection strings they expand automatically during normalization:
301
+ `llm-strings` ships provider detection, host aliases, metadata, and parameter
302
+ normalization for the major LLM provider shapes. Chat-compatible providers get
303
+ canonical parameter mapping and validation; media and audio providers are
304
+ available for detection, metadata, aliases, and flexible AI SDK providerOptions.
305
+
306
+ | Category | Providers |
307
+ | --------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
308
+ | Core chat + reasoning | OpenAI, Azure OpenAI, Anthropic, Google AI Studio, Google Vertex AI, Mistral, Cohere, AWS Bedrock, OpenRouter, Vercel AI Gateway |
309
+ | OpenAI-compatible APIs | xAI, Groq, DeepInfra, Together.ai, Fireworks, DeepSeek, Moonshot AI, Perplexity, Alibaba DashScope, Cerebras, Baseten, Hugging Face |
310
+ | Media, audio, and flexible | Fal, Black Forest Labs, Replicate, Prodia, Luma, ByteDance, Kling AI, ElevenLabs, AssemblyAI, Deepgram, Gladia, LMNT, Hume, Rev.ai |
311
+ | Extra aliases and endpoints | `aistudio`, `vertex`, `grok`, `bfl`, `dashscope`, `alibabacloud`, `togetherai`, `fireworksai`, `moonshot`, `wandb`, `weightsandbiases`, `baidu`, `qianfan`, `venice`, `parasail`, `novita`, `atlascloud`, `xiaomi`, `minimax` |
312
+
313
+ ### Provider table
314
+
315
+ | Provider ID | Default host | Param style |
316
+ | ------------------- | ----------------------------------------- | ----------------- |
317
+ | `openai` | `api.openai.com` | snake_case |
318
+ | `azure` | `models.inference.ai.azure.com` | OpenAI-compatible |
319
+ | `anthropic` | `api.anthropic.com` | snake_case |
320
+ | `google` | `generativelanguage.googleapis.com` | camelCase |
321
+ | `google-vertex` | `aiplatform.googleapis.com` | camelCase |
322
+ | `mistral` | `api.mistral.ai` | snake_case |
323
+ | `cohere` | `api.cohere.com` | mixed |
324
+ | `bedrock` | `bedrock-runtime.us-east-1.amazonaws.com` | camelCase |
325
+ | `openrouter` | `openrouter.ai` | OpenAI-compatible |
326
+ | `vercel` | `gateway.ai.vercel.app` | OpenAI-compatible |
327
+ | `xai` | `api.x.ai` | OpenAI-compatible |
328
+ | `groq` | `api.groq.com` | OpenAI-compatible |
329
+ | `fal` | `fal.run` | flexible |
330
+ | `deepinfra` | `api.deepinfra.com` | OpenAI-compatible |
331
+ | `black-forest-labs` | `api.bfl.ai` | flexible |
332
+ | `together` | `api.together.xyz` | OpenAI-compatible |
333
+ | `fireworks` | `api.fireworks.ai` | OpenAI-compatible |
334
+ | `deepseek` | `api.deepseek.com` | OpenAI-compatible |
335
+ | `moonshotai` | `api.moonshot.ai` | OpenAI-compatible |
336
+ | `perplexity` | `api.perplexity.ai` | OpenAI-compatible |
337
+ | `alibaba` | `dashscope-intl.aliyuncs.com` | OpenAI-compatible |
338
+ | `cerebras` | `api.cerebras.ai` | OpenAI-compatible |
339
+ | `replicate` | `api.replicate.com` | flexible |
340
+ | `prodia` | `api.prodia.com` | flexible |
341
+ | `luma` | `api.lumalabs.ai` | flexible |
342
+ | `bytedance` | `ark.cn-beijing.volces.com` | flexible |
343
+ | `kling` | `api.klingai.com` | flexible |
344
+ | `elevenlabs` | `api.elevenlabs.io` | flexible |
345
+ | `assemblyai` | `api.assemblyai.com` | flexible |
346
+ | `deepgram` | `api.deepgram.com` | flexible |
347
+ | `gladia` | `api.gladia.io` | flexible |
348
+ | `lmnt` | `api.lmnt.com` | flexible |
349
+ | `hume` | `api.hume.ai` | flexible |
350
+ | `revai` | `api.rev.ai` | flexible |
351
+ | `baseten` | `api.baseten.co` | OpenAI-compatible |
352
+ | `huggingface` | `api-inference.huggingface.co` | OpenAI-compatible |
353
+
354
+ ## Shorthand aliases
355
+
356
+ Use short, memorable query params. `normalize()` expands them first, then maps
357
+ them to provider-native names.
354
358
 
355
359
  | Shorthand | Canonical |
356
360
  | ------------------------------------------------------------------------------------------------------------ | ------------------- |
@@ -366,21 +370,48 @@ Use these shortcuts in your connection strings — they expand automatically dur
366
370
  | `reasoning`, `reasoning_effort` | `effort` |
367
371
  | `cache_control`, `cacheControl`, `cachePoint`, `cache_point` | `cache` |
368
372
 
369
- ## Sub-path Imports
373
+ ## Prompt caching
370
374
 
371
- For smaller bundles, import only what you need:
375
+ ```ts
376
+ import { normalize, parse } from "llm-strings";
377
+
378
+ normalize(parse("llm://anthropic/claude-sonnet-4-5?max=4096&cache=true")).config
379
+ .params;
380
+ // { max_tokens: "4096", cache_control: "ephemeral" }
381
+
382
+ normalize(parse("llm://anthropic/claude-sonnet-4-5?max=4096&cache=5m")).config
383
+ .params;
384
+ // { max_tokens: "4096", cache_control: "ephemeral", cache_ttl: "5m" }
385
+
386
+ normalize(
387
+ parse("llm://bedrock/anthropic.claude-sonnet-4-5-20250929-v1:0?cache=1h"),
388
+ ).config.params;
389
+ // { cache_control: "ephemeral", cache_ttl: "1h" }
390
+ ```
391
+
392
+ Caching currently normalizes for Anthropic and supported Bedrock models. For
393
+ Anthropic, `cache=5m` and `cache=1h` are both supported. For Bedrock, support is
394
+ model-family specific and currently covers Claude and Amazon Nova models. For
395
+ providers where caching is automatic, unsupported, or provider-specific in a way
396
+ that should not be represented as a generation param, `cache` is dropped during
397
+ normalization.
398
+
399
+ ## Sub-path imports
372
400
 
373
401
  ```ts
374
- import { parse, build } from "llm-strings/parse";
402
+ import { build, parse } from "llm-strings/parse";
375
403
  import { normalize } from "llm-strings/normalize";
376
404
  import { validate } from "llm-strings/validate";
405
+ import { createAiSdkProviderOptions } from "llm-strings/ai-sdk";
377
406
  import {
378
- detectProvider,
379
- resolveHostAlias,
380
407
  ALIASES,
408
+ CANONICAL_PARAM_SPECS,
381
409
  HOST_ALIASES,
382
- PROVIDER_PARAMS,
383
410
  PARAM_SPECS,
411
+ PROVIDER_META,
412
+ PROVIDER_PARAMS,
413
+ detectProvider,
414
+ resolveHostAlias,
384
415
  } from "llm-strings/providers";
385
416
  ```
386
417
 
@@ -390,151 +421,120 @@ All sub-paths ship ESM + CJS with full type declarations.
390
421
 
391
422
  ### `parse(connectionString): LlmConnectionConfig`
392
423
 
393
- Parses an `llm://` connection string into its component parts. Throws if the scheme is not `llm://`.
424
+ Parses an `llm://` connection string into structured config. Throws when the
425
+ scheme is not `llm://`.
394
426
 
395
427
  ### `build(config): string`
396
428
 
397
- Reconstructs a connection string from a config object. Inverse of `parse()`.
429
+ Builds an `llm://` connection string from a config object. This is the inverse
430
+ of `parse()`.
398
431
 
399
432
  ### `normalize(config, options?): NormalizeResult`
400
433
 
401
- Normalizes parameters for the target provider:
434
+ Normalizes params for the detected provider:
402
435
 
403
- 1. Expands shorthand aliases (`temp` `temperature`)
404
- 2. Maps to provider-specific param names (`max_tokens` `maxOutputTokens` for Google)
405
- 3. Normalizes cache values (`cache=true` `cache_control=ephemeral`)
406
- 4. Adjusts for reasoning models (`max_tokens` `max_completion_tokens` for o1/o3/o4)
436
+ 1. Expands shorthand aliases such as `temp` -> `temperature`.
437
+ 2. Maps canonical names to provider-specific names such as `max_tokens` ->
438
+ `maxOutputTokens` for Google.
439
+ 3. Normalizes cache values such as `cache=5m` -> `cache_control=ephemeral` and
440
+ `cache_ttl=5m`.
441
+ 4. Adjusts OpenAI reasoning-family params such as `max_tokens` ->
442
+ `max_completion_tokens` and drops known unsupported sampling params.
407
443
 
408
- Pass `{ verbose: true }` to get a detailed `changes` array documenting each transformation.
444
+ Pass `{ verbose: true }` to get a `changes` array that documents each
445
+ transformation.
409
446
 
410
447
  ### `validate(connectionString, options?): ValidationIssue[]`
411
448
 
412
- Parses, normalizes, and validates a connection string against provider-specific rules. Returns `[]` if everything is valid. Checks:
413
-
414
- - Type correctness (number, boolean, string enums)
415
- - Value ranges (e.g., temperature 0–2 for OpenAI, 0–1 for Anthropic)
416
- - Mutual exclusions (`temperature` + `top_p` on Anthropic)
417
- - Reasoning model restrictions (no `temperature` on o1/o3/o4)
418
- - Bedrock model family constraints (`topK` only for Claude/Cohere/Mistral)
419
-
420
- Pass `{ strict: true }` to promote warnings (unknown provider, unknown params) to errors:
421
-
422
- ```ts
423
- validate("llm://custom-api.com/my-model?temp=0.5", { strict: true });
424
- // → [{ severity: "error", message: "Unknown provider …" }]
425
- ```
426
-
427
- ### `detectProvider(host): Provider | undefined`
428
-
429
- Identifies the provider from a hostname string.
430
-
431
- ### `resolveHostAlias(host): HostResolution`
432
-
433
- Expands provider short host aliases such as `"openai"` to canonical hosts such as `"api.openai.com"`. Reads `LLM_STRINGS_<PROVIDER>_HOST` and `LLM_STRINGS_HOST_<PROVIDER>` env overrides when available.
434
-
435
- ### `detectBedrockModelFamily(model): BedrockModelFamily | undefined`
436
-
437
- Identifies the model family (anthropic, meta, amazon, mistral, cohere, ai21) from a Bedrock model ID. Handles cross-region (`us.`, `eu.`, `apac.`) and global inference profiles.
438
-
439
- ### `detectGatewaySubProvider(model): Provider | undefined`
440
-
441
- Extracts the underlying provider from a gateway model string (e.g. `"anthropic/claude-sonnet-4-5"` → `"anthropic"`). Returns `undefined` for unknown prefixes or models without a `/`.
442
-
443
- ### `isReasoningModel(model): boolean`
444
-
445
- Returns `true` for OpenAI reasoning models (o1, o3, o4 families). Handles gateway prefixes like `"openai/o3"`.
449
+ Parses, normalizes, and validates a connection string. Returns `[]` when the
450
+ config is valid. Checks include type correctness, numeric ranges, enum values,
451
+ unknown providers, Anthropic `temperature` + `top_p` mutual exclusion,
452
+ OpenAI reasoning-family normalization, and Bedrock model-family rules. Unknown
453
+ params are allowed by default so new model-specific schemas can pass through.
446
454
 
447
- ### `isGatewayProvider(provider): boolean`
455
+ Pass `{ strict: true }` to report unknown providers or unknown params as
456
+ errors.
448
457
 
449
- Returns `true` for gateway providers (`openrouter`, `vercel`) that proxy to other providers.
458
+ ### `createAiSdkProviderOptions(input, options?): AiSdkProviderOptionsResult`
450
459
 
451
- ### `canHostOpenAIModels(provider): boolean`
460
+ Creates AI SDK `providerOptions` from a string, parsed config, or normalize
461
+ result. Import from `llm-strings/ai-sdk`.
452
462
 
453
- Returns `true` for providers that can route to OpenAI models and need reasoning-model checks (`openai`, `openrouter`, `vercel`).
463
+ ### Provider helpers
454
464
 
455
- ### `bedrockSupportsCaching(model): boolean`
465
+ Import from `llm-strings/providers`:
456
466
 
457
- Returns `true` if the Bedrock model supports prompt caching (Claude and Nova models only).
467
+ | Export | Description |
468
+ | ------------------------------- | ------------------------------------------------------------------------------------- |
469
+ | `detectProvider(host)` | Detects provider from hostname. |
470
+ | `resolveHostAlias(host)` | Expands aliases and applies `LLM_STRINGS_*_HOST` overrides. |
471
+ | `detectBedrockModelFamily()` | Detects Anthropic, Meta, Amazon, Mistral, Cohere, or AI21 Bedrock families. |
472
+ | `detectGatewaySubProvider()` | Extracts provider prefix from gateway models like `anthropic/claude...`. |
473
+ | `isReasoningModel(model)` | Detects OpenAI GPT-5 and o-series reasoning models, including gateway-prefixed names. |
474
+ | `isGatewayProvider(provider)` | Returns true for `openrouter` and `vercel`. |
475
+ | `canHostOpenAIModels(provider)` | Returns true for providers that need OpenAI reasoning-model checks. |
476
+ | `bedrockSupportsCaching(model)` | Returns true for Bedrock Claude and Nova prompt caching support. |
458
477
 
459
478
  ### Constants
460
479
 
461
- | Export | Description |
462
- | ----------------------------- | ------------------------------------------------------------------------------------------ |
463
- | `ALIASES` | Shorthand canonical param name mapping |
464
- | `HOST_ALIASES` | Short provider host aliases canonical API hosts |
465
- | `PROVIDER_PARAMS` | Canonical provider-specific param names, per provider |
466
- | `PARAM_SPECS` | Validation rules (type, min/max, enum) per provider, keyed by provider-specific param name |
467
- | `REASONING_MODEL_UNSUPPORTED` | Set of canonical params unsupported by reasoning models |
468
- | `PROVIDER_META` | Array of provider metadata (id, name, host, brand color) for UI integrations |
469
- | `MODELS` | Suggested model IDs per provider |
470
- | `CANONICAL_PARAM_SPECS` | Canonical param specs per provider with descriptions — useful for building UIs |
480
+ | Export | Description |
481
+ | ----------------------------- | ------------------------------------------------------------------------------- |
482
+ | `ALIASES` | Shorthand -> canonical param name mapping. |
483
+ | `HOST_ALIASES` | Short provider host aliases -> canonical API hosts. |
484
+ | `PROVIDER_PARAMS` | Canonical -> provider-specific param names, per provider. |
485
+ | `PARAM_SPECS` | Validation rules keyed by provider-specific param name. |
486
+ | `REASONING_MODEL_UNSUPPORTED` | Canonical params unsupported by OpenAI reasoning models. |
487
+ | `PROVIDER_META` | Provider metadata for UI integrations. |
488
+ | `CANONICAL_PARAM_SPECS` | Canonical param specs per provider, useful for building forms and settings UIs. |
471
489
 
472
490
  ## TypeScript
473
491
 
474
- Full type definitions ship with the package:
475
-
476
492
  ```ts
477
- // Core types from the main entry
478
493
  import type {
479
494
  LlmConnectionConfig,
480
- NormalizeResult,
481
495
  NormalizeChange,
482
496
  NormalizeOptions,
497
+ NormalizeResult,
483
498
  ValidateOptions,
484
499
  ValidationIssue,
485
500
  } from "llm-strings";
486
501
 
487
- // Provider types from the providers sub-path
488
502
  import type {
489
- Provider,
490
503
  BedrockModelFamily,
504
+ CanonicalParamSpec,
491
505
  ParamSpec,
506
+ Provider,
492
507
  ProviderMeta,
493
- CanonicalParamSpec,
494
508
  } from "llm-strings/providers";
495
509
  ```
496
510
 
497
- ## Provider Metadata (for UI integrations)
498
-
499
- The library exports metadata useful for building UIs — provider names, brand colors, suggested models, and canonical parameter specs:
511
+ ## Development
500
512
 
501
- ```ts
502
- import {
503
- PROVIDER_META,
504
- MODELS,
505
- CANONICAL_PARAM_SPECS,
506
- } from "llm-strings/providers";
507
-
508
- // Provider display info
509
- PROVIDER_META.forEach((p) => console.log(`${p.name}: ${p.host} (${p.color})`));
510
- // OpenAI: api.openai.com (#10a37f)
511
- // Anthropic: api.anthropic.com (#e8956a)
512
- // ...
513
-
514
- // Suggested models per provider
515
- MODELS.openai; // → ["gpt-5.2", "gpt-5.2-pro", "gpt-4.1", "gpt-4.1-mini", ...]
516
- MODELS.anthropic; // → ["claude-opus-4-6", "claude-sonnet-4-6", "claude-sonnet-4-5", ...]
517
-
518
- // Canonical param specs — useful for building config forms
519
- CANONICAL_PARAM_SPECS.openai.temperature;
520
- // → { type: "number", min: 0, max: 2, default: 0.7, description: "Controls randomness" }
521
-
522
- CANONICAL_PARAM_SPECS.anthropic.effort;
523
- // → { type: "enum", values: ["low", "medium", "high", "max"], default: "medium", description: "Thinking effort" }
513
+ ```bash
514
+ pnpm install
515
+ pnpm test
516
+ pnpm run build
517
+ pnpm run lint
524
518
  ```
525
519
 
520
+ This package is intentionally small: pure TypeScript, zero runtime dependencies,
521
+ and focused tests for parsing, normalization, validation, provider metadata,
522
+ Bedrock behavior, gateway behavior, and AI SDK providerOptions.
523
+
526
524
  ## Contributing
527
525
 
528
- Contributions are welcome! Please feel free to submit a Pull Request.
526
+ Issues and pull requests are welcome. Good contributions include new provider
527
+ aliases, provider-specific validation rules, improved normalization mappings,
528
+ AI SDK providerOptions coverage, docs fixes, and real-world edge cases.
529
529
 
530
530
  ## License
531
531
 
532
- MIT
532
+ MIT © Dan Levy
533
533
 
534
534
  ---
535
535
 
536
536
  <div align="center">
537
537
 
538
- **[📖 Read the spec](https://danlevy.net/llm-connection-strings/) · [🐛 Report a bug](https://github.com/justsml/llm-strings/issues) · [💡 Request a feature](https://github.com/justsml/llm-strings/issues)**
538
+ **[Read the spec](https://danlevy.net/llm-connection-strings/) · [Report a bug](https://github.com/justsml/llm-strings/issues) · [Request a feature](https://github.com/justsml/llm-strings/issues)**
539
539
 
540
540
  </div>