llm-strings 1.4.0 → 1.5.1

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