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/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
- ```
135
-
136
- ### Short host aliases
74
+ import { build, normalize, parse, validate } from "llm-strings";
137
75
 
138
- Provider IDs can be used as short hostnames:
76
+ const input = "llm://openai/gpt-4o?temp=0.7&max=2000&topp=0.9";
139
77
 
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:
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.
163
125
 
164
- ```ts
165
- import { validate } from "llm-strings";
126
+ ## Examples
166
127
 
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
- );
128
+ ### One env var for your model config
171
129
 
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
- ```
177
-
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";
219
+ import { validate } from "llm-strings";
268
220
 
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
- );
221
+ validate("llm://openai/gpt-4o?temp=3.0");
222
+ // [{ param: "temperature", message: "\"temperature\" must be <= 2, got 3", ... }]
275
223
 
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
- ```
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
285
226
 
286
- ### Building connection strings programmatically
227
+ validate("llm://fal/fal-ai/flux-pro?future_model_param=1");
228
+ // [] — unknown params pass through by default for model-specific schemas
287
229
 
288
- ```ts
289
- import { build } from "llm-strings";
290
-
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`.
337
280
 
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 |
281
+ ### Build provider-aware UIs
348
282
 
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.
283
+ ```ts
284
+ import { CANONICAL_PARAM_SPECS, PROVIDER_META } from "llm-strings/providers";
350
285
 
351
- ## Shorthand Aliases
286
+ PROVIDER_META.map(({ id, name, host, color }) => ({ id, name, host, color }));
287
+ // [{ id: "openai", name: "OpenAI", host: "api.openai.com", color: "#10a37f" }, ...]
352
288
 
353
- Use these shortcuts in your connection strings — they expand automatically during normalization:
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
+ ```
298
+
299
+ ## Supported Providers
300
+
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
374
+
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" }
370
385
 
371
- For smaller bundles, import only what you need:
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,142 +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`
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.
436
454
 
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.
455
+ Pass `{ strict: true }` to report unknown providers or unknown params as
456
+ errors.
438
457
 
439
- ### `detectGatewaySubProvider(model): Provider | undefined`
458
+ ### `createAiSdkProviderOptions(input, options?): AiSdkProviderOptionsResult`
440
459
 
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 `/`.
460
+ Creates AI SDK `providerOptions` from a string, parsed config, or normalize
461
+ result. Import from `llm-strings/ai-sdk`.
442
462
 
443
- ### `isReasoningModel(model): boolean`
463
+ ### Provider helpers
444
464
 
445
- Returns `true` for OpenAI reasoning models (o1, o3, o4 families). Handles gateway prefixes like `"openai/o3"`.
465
+ Import from `llm-strings/providers`:
446
466
 
447
- ### `isGatewayProvider(provider): boolean`
448
-
449
- Returns `true` for gateway providers (`openrouter`, `vercel`) that proxy to other providers.
450
-
451
- ### `canHostOpenAIModels(provider): boolean`
452
-
453
- Returns `true` for providers that can route to OpenAI models and need reasoning-model checks (`openai`, `openrouter`, `vercel`).
454
-
455
- ### `bedrockSupportsCaching(model): boolean`
456
-
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
- | `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. |
470
489
 
471
490
  ## TypeScript
472
491
 
473
- Full type definitions ship with the package:
474
-
475
492
  ```ts
476
- // Core types from the main entry
477
493
  import type {
478
494
  LlmConnectionConfig,
479
- NormalizeResult,
480
495
  NormalizeChange,
481
496
  NormalizeOptions,
497
+ NormalizeResult,
482
498
  ValidateOptions,
483
499
  ValidationIssue,
484
500
  } from "llm-strings";
485
501
 
486
- // Provider types from the providers sub-path
487
502
  import type {
488
- Provider,
489
503
  BedrockModelFamily,
504
+ CanonicalParamSpec,
490
505
  ParamSpec,
506
+ Provider,
491
507
  ProviderMeta,
492
- CanonicalParamSpec,
493
508
  } from "llm-strings/providers";
494
509
  ```
495
510
 
496
- ## Provider Metadata (for UI integrations)
497
-
498
- The library exports metadata useful for building UIs — provider names, brand colors, and canonical parameter specs:
499
-
500
- ```ts
501
- import { PROVIDER_META, CANONICAL_PARAM_SPECS } from "llm-strings/providers";
511
+ ## Development
502
512
 
503
- // Provider display info
504
- PROVIDER_META.forEach((p) => console.log(`${p.name}: ${p.host} (${p.color})`));
505
- // OpenAI: api.openai.com (#10a37f)
506
- // Anthropic: api.anthropic.com (#e8956a)
507
- // ...
508
-
509
- // Canonical param specs — useful for building config forms
510
- CANONICAL_PARAM_SPECS.openai.temperature;
511
- // → { type: "number", min: 0, max: 2, default: 0.7, description: "Controls randomness" }
512
-
513
- CANONICAL_PARAM_SPECS.anthropic.effort;
514
- // → { 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
515
518
  ```
516
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
+
517
524
  ## Contributing
518
525
 
519
- 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.
520
529
 
521
530
  ## License
522
531
 
523
- MIT
532
+ MIT © Dan Levy
524
533
 
525
534
  ---
526
535
 
527
536
  <div align="center">
528
537
 
529
- **[📖 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)**
530
539
 
531
540
  </div>