llm-provider-registry 1.0.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/LICENSE +21 -0
- package/README.md +115 -0
- package/index.d.ts +40 -0
- package/index.mjs +71 -0
- package/package.json +51 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 oratis
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
# llm-provider-registry
|
|
2
|
+
|
|
3
|
+
**Give it a model name, get back where to send the request and which key to use.**
|
|
4
|
+
|
|
5
|
+
```
|
|
6
|
+
"glm-4.6" → https://open.bigmodel.cn/api/paas/v4 · ZHIPU_API_KEY · OpenAI-compatible
|
|
7
|
+
"deepseek-chat" → https://api.deepseek.com/v1 · DEEPSEEK_API_KEY · OpenAI-compatible
|
|
8
|
+
"kimi-k2" → https://api.moonshot.cn/v1 · MOONSHOT_API_KEY · OpenAI-compatible
|
|
9
|
+
"claude-sonnet-4-6" → https://api.anthropic.com · ANTHROPIC_API_KEY· Anthropic SDK
|
|
10
|
+
"gpt-5" → https://api.openai.com/v1 · OPENAI_API_KEY · OpenAI SDK
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
A tiny, **zero-dependency** lookup table + resolver. It knows **16 providers** — including the Chinese ones (Zhipu/GLM, DeepSeek, Moonshot/Kimi, Qwen, Doubao, MiniMax, Baichuan, Yi, Stepfun, Hunyuan) that the big frameworks make you wire up by hand.
|
|
14
|
+
|
|
15
|
+
## The problem it solves
|
|
16
|
+
|
|
17
|
+
You want to let users pick *any* model — `gpt-5`, `deepseek-chat`, `glm-4.6`, `qwen3-max`, `claude-…` — and Just Work. Every one of those is reachable through the **OpenAI chat-completions API**, so in principle you only need one SDK. The catch is the plumbing: each provider has its own base URL and its own API-key env var, and nobody ships a maintained table of them — the OpenAI SDK obviously doesn't, and the big frameworks skip most of the Chinese providers.
|
|
18
|
+
|
|
19
|
+
So everyone ends up hand-copying base URLs off docs pages into a `switch` statement. This is that `switch` statement, curated and tested, as a 2 kB import.
|
|
20
|
+
|
|
21
|
+
```js
|
|
22
|
+
import OpenAI from "openai";
|
|
23
|
+
import { resolveModel } from "llm-provider-registry";
|
|
24
|
+
|
|
25
|
+
function clientFor(model) {
|
|
26
|
+
const p = resolveModel(model);
|
|
27
|
+
if (!p) throw new Error(`Unknown model: ${model}`);
|
|
28
|
+
if (!p.openaiCompatible) throw new Error(`${p.provider} needs its own SDK, not OpenAI's`);
|
|
29
|
+
return new OpenAI({ baseURL: p.baseURL, apiKey: p.apiKey });
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// One line, any provider:
|
|
33
|
+
const res = await clientFor("glm-4.6").chat.completions.create({
|
|
34
|
+
model: "glm-4.6",
|
|
35
|
+
messages: [{ role: "user", content: "hi" }],
|
|
36
|
+
});
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## Install
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
npm install llm-provider-registry
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Node ≥ 18. ESM. Ships its own TypeScript types.
|
|
46
|
+
|
|
47
|
+
## API
|
|
48
|
+
|
|
49
|
+
### `resolveModel(model, env?) → ResolvedModel | null`
|
|
50
|
+
|
|
51
|
+
Returns `null` if the model isn't recognized, otherwise:
|
|
52
|
+
|
|
53
|
+
```ts
|
|
54
|
+
{
|
|
55
|
+
provider: string; // "Zhipu (GLM)", "DeepSeek", "Anthropic", …
|
|
56
|
+
kind: "anthropic" | "openai" | "gemini" | "openai-compatible";
|
|
57
|
+
baseURL: string; // point your client here
|
|
58
|
+
apiKeyEnv: string; // e.g. "ZHIPU_API_KEY"
|
|
59
|
+
apiKey: string | undefined;// that var's value, read from `env` (default process.env)
|
|
60
|
+
openaiCompatible: boolean; // true → use the OpenAI SDK against baseURL
|
|
61
|
+
}
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
`env` defaults to `process.env`; pass your own object to resolve keys from somewhere else (tests, a config store, per-request overrides).
|
|
65
|
+
|
|
66
|
+
**`openaiCompatible` is the branch that matters.** It's `true` for everything except Anthropic (`claude-*`), which speaks a different wire protocol — send those to `@anthropic-ai/sdk`. OpenAI, Gemini (via its OpenAI-compat endpoint), and all 13 presets are `true`.
|
|
67
|
+
|
|
68
|
+
### `hasCredentials(model, env?) → boolean`
|
|
69
|
+
|
|
70
|
+
`true` only when the model is recognized **and** its API-key env var is set. Handy for filtering a model menu down to the ones the user can actually call:
|
|
71
|
+
|
|
72
|
+
```js
|
|
73
|
+
const usable = ALL_MODELS.filter((m) => hasCredentials(m));
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
### `PRESETS`
|
|
77
|
+
|
|
78
|
+
The raw table of OpenAI-compatible third-party providers, if you'd rather iterate it yourself:
|
|
79
|
+
|
|
80
|
+
```ts
|
|
81
|
+
{ name: string; modelPrefixes: string[]; baseURL: string; apiKeyEnv: string }[]
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
## Providers covered
|
|
85
|
+
|
|
86
|
+
| Provider | Example model prefixes | Base URL | API-key env |
|
|
87
|
+
|---|---|---|---|
|
|
88
|
+
| **Anthropic** | `claude-` | `api.anthropic.com` | `ANTHROPIC_API_KEY` |
|
|
89
|
+
| **OpenAI** | `gpt-`, `o1`, `o3`, `o4`, `chatgpt-` | `api.openai.com/v1` | `OPENAI_API_KEY` |
|
|
90
|
+
| **Google Gemini** | `gemini-` | `…/v1beta/openai/` | `GEMINI_API_KEY` |
|
|
91
|
+
| DeepSeek | `deepseek-` | `api.deepseek.com/v1` | `DEEPSEEK_API_KEY` |
|
|
92
|
+
| Mistral AI | `mistral-`, `codestral-`, `magistral-`, `ministral-`, `pixtral-` | `api.mistral.ai/v1` | `MISTRAL_API_KEY` |
|
|
93
|
+
| Perplexity (Sonar) | `sonar`, `sonar-` | `api.perplexity.ai` | `PERPLEXITY_API_KEY` |
|
|
94
|
+
| xAI Grok | `grok-` | `api.x.ai/v1` | `XAI_API_KEY` |
|
|
95
|
+
| Volcengine Ark (Doubao) | `doubao-`, `ep-` | `ark.cn-beijing.volces.com/api/v3` | `ARK_API_KEY` |
|
|
96
|
+
| Aliyun DashScope (Qwen) | `qwen-`, `qwen2`, `qwen3` | `dashscope.aliyuncs.com/compatible-mode/v1` | `DASHSCOPE_API_KEY` |
|
|
97
|
+
| Moonshot (Kimi) | `moonshot-`, `kimi-` | `api.moonshot.cn/v1` | `MOONSHOT_API_KEY` |
|
|
98
|
+
| Zhipu (GLM) | `glm-`, `chatglm-` | `open.bigmodel.cn/api/paas/v4` | `ZHIPU_API_KEY` |
|
|
99
|
+
| Stepfun (Step) | `step-` | `api.stepfun.com/v1` | `STEPFUN_API_KEY` |
|
|
100
|
+
| 01.AI (Yi) | `yi-` | `api.lingyiwanwu.com/v1` | `LINGYI_API_KEY` |
|
|
101
|
+
| Baichuan | `baichuan-`, `baichuan2/3/4` | `api.baichuan-ai.com/v1` | `BAICHUAN_API_KEY` |
|
|
102
|
+
| MiniMax | `abab`, `minimax-` | `api.minimax.io/v1` | `MINIMAX_API_KEY` |
|
|
103
|
+
| Tencent Hunyuan | `hunyuan-` | `api.hunyuan.cloud.tencent.com/v1` | `HUNYUAN_API_KEY` |
|
|
104
|
+
|
|
105
|
+
Matching is by **case-insensitive prefix**, so `glm-4.6`, `glm-4-flash`, and `GLM-4.6` all resolve to Zhipu — new model releases usually keep the family prefix and Just Work without a library bump.
|
|
106
|
+
|
|
107
|
+
## What it deliberately isn't
|
|
108
|
+
|
|
109
|
+
- **Not an API client.** It hands you a `baseURL` + key; you bring your own SDK. That's the point — it stays 2 kB and never breaks when a provider tweaks a response field.
|
|
110
|
+
- **Not a param translator.** It doesn't reshape requests between the Anthropic and OpenAI schemas. It only tells you which family a model belongs to (`kind`) so you know which SDK to reach for.
|
|
111
|
+
- **Not exhaustive on models.** It matches *families* by prefix, not an ever-changing list of exact model IDs.
|
|
112
|
+
|
|
113
|
+
## License
|
|
114
|
+
|
|
115
|
+
MIT © 2026 oratis
|
package/index.d.ts
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
export interface Preset {
|
|
2
|
+
name: string;
|
|
3
|
+
modelPrefixes: string[];
|
|
4
|
+
baseURL: string;
|
|
5
|
+
apiKeyEnv: string;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
/** OpenAI-compatible third-party providers, keyed by model-name prefix. */
|
|
9
|
+
export const PRESETS: Preset[];
|
|
10
|
+
|
|
11
|
+
export type ProviderKind = "anthropic" | "openai" | "gemini" | "openai-compatible";
|
|
12
|
+
|
|
13
|
+
export interface ResolvedModel {
|
|
14
|
+
/** Human-readable provider name, e.g. "Zhipu (GLM)". */
|
|
15
|
+
provider: string;
|
|
16
|
+
kind: ProviderKind;
|
|
17
|
+
/** API base URL to point your client at. */
|
|
18
|
+
baseURL: string;
|
|
19
|
+
/** Env var that holds this provider's API key. */
|
|
20
|
+
apiKeyEnv: string;
|
|
21
|
+
/** The key's value from the provided env (or undefined if unset). */
|
|
22
|
+
apiKey: string | undefined;
|
|
23
|
+
/** True when you can use the OpenAI chat-completions SDK against `baseURL`. */
|
|
24
|
+
openaiCompatible: boolean;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Resolve a model name to its provider endpoint + credential.
|
|
29
|
+
* Returns `null` if the model isn't recognized.
|
|
30
|
+
*/
|
|
31
|
+
export function resolveModel(
|
|
32
|
+
model: string,
|
|
33
|
+
env?: Record<string, string | undefined>,
|
|
34
|
+
): ResolvedModel | null;
|
|
35
|
+
|
|
36
|
+
/** True iff the model is recognized AND its API-key env var is set. */
|
|
37
|
+
export function hasCredentials(
|
|
38
|
+
model: string,
|
|
39
|
+
env?: Record<string, string | undefined>,
|
|
40
|
+
): boolean;
|
package/index.mjs
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
// llm-provider-registry — map an LLM model name to the endpoint + credential it
|
|
2
|
+
// needs. Given "glm-4.6", "deepseek-chat", "kimi-k2", "grok-4", "claude-…",
|
|
3
|
+
// "gpt-…", tells you the base URL, which API-key env var, and whether it speaks
|
|
4
|
+
// the OpenAI chat-completions API — so you can point one SDK anywhere.
|
|
5
|
+
//
|
|
6
|
+
// Curated for 16 providers the big frameworks don't cover (esp. Chinese ones).
|
|
7
|
+
// Zero dependencies. Pure data + string matching.
|
|
8
|
+
|
|
9
|
+
/** @typedef {{ name: string, modelPrefixes: string[], baseURL: string, apiKeyEnv: string }} Preset */
|
|
10
|
+
|
|
11
|
+
/** OpenAI-compatible third-party providers, keyed by model-name prefix. */
|
|
12
|
+
export const PRESETS = [
|
|
13
|
+
// ── International ──
|
|
14
|
+
{ name: "DeepSeek", modelPrefixes: ["deepseek-"], baseURL: "https://api.deepseek.com/v1", apiKeyEnv: "DEEPSEEK_API_KEY" },
|
|
15
|
+
{ name: "Mistral AI", modelPrefixes: ["mistral-", "codestral-", "magistral-", "ministral-", "pixtral-"], baseURL: "https://api.mistral.ai/v1", apiKeyEnv: "MISTRAL_API_KEY" },
|
|
16
|
+
{ name: "Perplexity (Sonar)", modelPrefixes: ["sonar-", "sonar"], baseURL: "https://api.perplexity.ai", apiKeyEnv: "PERPLEXITY_API_KEY" },
|
|
17
|
+
{ name: "xAI Grok", modelPrefixes: ["grok-"], baseURL: "https://api.x.ai/v1", apiKeyEnv: "XAI_API_KEY" },
|
|
18
|
+
// ── Chinese ──
|
|
19
|
+
{ name: "Volcengine Ark (Doubao)", modelPrefixes: ["doubao-", "ep-"], baseURL: "https://ark.cn-beijing.volces.com/api/v3", apiKeyEnv: "ARK_API_KEY" },
|
|
20
|
+
{ name: "Aliyun DashScope (Qwen)", modelPrefixes: ["qwen-", "qwen2", "qwen3"], baseURL: "https://dashscope.aliyuncs.com/compatible-mode/v1", apiKeyEnv: "DASHSCOPE_API_KEY" },
|
|
21
|
+
{ name: "Moonshot (Kimi)", modelPrefixes: ["moonshot-", "kimi-"], baseURL: "https://api.moonshot.cn/v1", apiKeyEnv: "MOONSHOT_API_KEY" },
|
|
22
|
+
{ name: "Zhipu (GLM)", modelPrefixes: ["glm-", "chatglm-"], baseURL: "https://open.bigmodel.cn/api/paas/v4", apiKeyEnv: "ZHIPU_API_KEY" },
|
|
23
|
+
{ name: "Stepfun (Step)", modelPrefixes: ["step-"], baseURL: "https://api.stepfun.com/v1", apiKeyEnv: "STEPFUN_API_KEY" },
|
|
24
|
+
{ name: "01.AI (Yi)", modelPrefixes: ["yi-"], baseURL: "https://api.lingyiwanwu.com/v1", apiKeyEnv: "LINGYI_API_KEY" },
|
|
25
|
+
{ name: "Baichuan", modelPrefixes: ["baichuan-", "baichuan2", "baichuan3", "baichuan4"], baseURL: "https://api.baichuan-ai.com/v1", apiKeyEnv: "BAICHUAN_API_KEY" },
|
|
26
|
+
{ name: "MiniMax", modelPrefixes: ["abab", "minimax-"], baseURL: "https://api.minimax.io/v1", apiKeyEnv: "MINIMAX_API_KEY" },
|
|
27
|
+
{ name: "Tencent Hunyuan", modelPrefixes: ["hunyuan-"], baseURL: "https://api.hunyuan.cloud.tencent.com/v1", apiKeyEnv: "HUNYUAN_API_KEY" },
|
|
28
|
+
];
|
|
29
|
+
|
|
30
|
+
// First-party providers (matched before the presets).
|
|
31
|
+
const FIRST_PARTY = [
|
|
32
|
+
{ name: "Anthropic", kind: "anthropic", test: (m) => m.startsWith("claude-"),
|
|
33
|
+
baseURL: "https://api.anthropic.com", apiKeyEnv: "ANTHROPIC_API_KEY", openaiCompatible: false },
|
|
34
|
+
{ name: "OpenAI", kind: "openai", test: (m) => /^(gpt-|o1|o3|o4|chatgpt-)/.test(m),
|
|
35
|
+
baseURL: "https://api.openai.com/v1", apiKeyEnv: "OPENAI_API_KEY", openaiCompatible: true },
|
|
36
|
+
{ name: "Google Gemini", kind: "gemini", test: (m) => m.startsWith("gemini-"),
|
|
37
|
+
baseURL: "https://generativelanguage.googleapis.com/v1beta/openai/", apiKeyEnv: "GEMINI_API_KEY", openaiCompatible: true },
|
|
38
|
+
];
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Resolve a model name to its provider endpoint + credential.
|
|
42
|
+
* @param {string} model e.g. "glm-4.6", "deepseek-chat", "claude-sonnet-4-6", "gpt-5"
|
|
43
|
+
* @param {Record<string,string|undefined>} [env] where to read the API key (default process.env)
|
|
44
|
+
* @returns {{ provider: string, kind: "anthropic"|"openai"|"gemini"|"openai-compatible",
|
|
45
|
+
* baseURL: string, apiKeyEnv: string, apiKey: string|undefined,
|
|
46
|
+
* openaiCompatible: boolean } | null} null if the model isn't recognized.
|
|
47
|
+
*/
|
|
48
|
+
export function resolveModel(model, env = process.env) {
|
|
49
|
+
if (!model) return null;
|
|
50
|
+
const m = String(model).toLowerCase();
|
|
51
|
+
|
|
52
|
+
for (const p of FIRST_PARTY) {
|
|
53
|
+
if (p.test(m)) {
|
|
54
|
+
return { provider: p.name, kind: p.kind, baseURL: p.baseURL, apiKeyEnv: p.apiKeyEnv,
|
|
55
|
+
apiKey: env?.[p.apiKeyEnv], openaiCompatible: p.openaiCompatible };
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
for (const p of PRESETS) {
|
|
59
|
+
if (p.modelPrefixes.some((pre) => m.startsWith(pre.toLowerCase()))) {
|
|
60
|
+
return { provider: p.name, kind: "openai-compatible", baseURL: p.baseURL, apiKeyEnv: p.apiKeyEnv,
|
|
61
|
+
apiKey: env?.[p.apiKeyEnv], openaiCompatible: true };
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** True iff the model is recognized AND its API-key env var is set (non-empty). */
|
|
68
|
+
export function hasCredentials(model, env = process.env) {
|
|
69
|
+
const r = resolveModel(model, env);
|
|
70
|
+
return !!(r && r.apiKey && r.apiKey.trim());
|
|
71
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "llm-provider-registry",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Map any LLM model name (glm-4.6, deepseek-chat, kimi-k2, grok-4, claude-…, gpt-…) to its API base URL + key env var. Curated for 16 providers — incl. the Chinese ones the big frameworks don't ship. Zero deps.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "index.mjs",
|
|
7
|
+
"types": "index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./index.d.ts",
|
|
11
|
+
"default": "./index.mjs"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"files": [
|
|
15
|
+
"index.mjs",
|
|
16
|
+
"index.d.ts",
|
|
17
|
+
"README.md",
|
|
18
|
+
"LICENSE"
|
|
19
|
+
],
|
|
20
|
+
"keywords": [
|
|
21
|
+
"llm",
|
|
22
|
+
"openai",
|
|
23
|
+
"anthropic",
|
|
24
|
+
"gemini",
|
|
25
|
+
"deepseek",
|
|
26
|
+
"qwen",
|
|
27
|
+
"glm",
|
|
28
|
+
"kimi",
|
|
29
|
+
"moonshot",
|
|
30
|
+
"doubao",
|
|
31
|
+
"grok",
|
|
32
|
+
"mistral",
|
|
33
|
+
"provider",
|
|
34
|
+
"router",
|
|
35
|
+
"base-url",
|
|
36
|
+
"openai-compatible"
|
|
37
|
+
],
|
|
38
|
+
"engines": {
|
|
39
|
+
"node": ">=18"
|
|
40
|
+
},
|
|
41
|
+
"scripts": {
|
|
42
|
+
"test": "node --test"
|
|
43
|
+
},
|
|
44
|
+
"license": "MIT",
|
|
45
|
+
"author": "oratis",
|
|
46
|
+
"repository": {
|
|
47
|
+
"type": "git",
|
|
48
|
+
"url": "git+https://github.com/oratis/llm-provider-registry.git"
|
|
49
|
+
},
|
|
50
|
+
"homepage": "https://github.com/oratis/llm-provider-registry#readme"
|
|
51
|
+
}
|