@khanglvm/llm-router 1.0.5
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/.env.test-suite.example +19 -0
- package/README.md +230 -0
- package/package.json +26 -0
- package/src/cli/router-module.js +3987 -0
- package/src/cli-entry.js +144 -0
- package/src/index.js +18 -0
- package/src/node/config-store.js +74 -0
- package/src/node/config-workflows.js +245 -0
- package/src/node/instance-state.js +206 -0
- package/src/node/local-server.js +294 -0
- package/src/node/provider-probe.js +905 -0
- package/src/node/start-command.js +498 -0
- package/src/node/startup-manager.js +369 -0
- package/src/runtime/config.js +655 -0
- package/src/runtime/handler/auth.js +32 -0
- package/src/runtime/handler/config-loading.js +45 -0
- package/src/runtime/handler/fallback.js +424 -0
- package/src/runtime/handler/http.js +71 -0
- package/src/runtime/handler/network-guards.js +137 -0
- package/src/runtime/handler/provider-call.js +245 -0
- package/src/runtime/handler/provider-translation.js +232 -0
- package/src/runtime/handler/request.js +194 -0
- package/src/runtime/handler/utils.js +41 -0
- package/src/runtime/handler.js +301 -0
- package/src/translator/formats.js +7 -0
- package/src/translator/index.js +73 -0
- package/src/translator/request/claude-to-openai.js +228 -0
- package/src/translator/request/openai-to-claude.js +241 -0
- package/src/translator/response/claude-to-openai.js +204 -0
- package/src/translator/response/openai-to-claude.js +197 -0
- package/wrangler.toml +20 -0
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
LLM_ROUTER_TEST_HOST=127.0.0.1
|
|
2
|
+
LLM_ROUTER_TEST_PORT=8787
|
|
3
|
+
LLM_ROUTER_TEST_TIMEOUT_MS=90000
|
|
4
|
+
|
|
5
|
+
# Test hostname/zone for routing tests (use local example for CI/public)
|
|
6
|
+
LLM_ROUTER_TEST_HOSTNAME=router.example.com
|
|
7
|
+
LLM_ROUTER_TEST_ZONE_NAME=example.com
|
|
8
|
+
|
|
9
|
+
LLM_ROUTER_TEST_PROVIDER_KEYS=EXAMPLE
|
|
10
|
+
|
|
11
|
+
LLM_ROUTER_TEST_EXAMPLE_PROVIDER_ID=example
|
|
12
|
+
LLM_ROUTER_TEST_EXAMPLE_NAME="Example Provider"
|
|
13
|
+
LLM_ROUTER_TEST_EXAMPLE_API_KEY=sk-...
|
|
14
|
+
LLM_ROUTER_TEST_EXAMPLE_OPENAI_BASE_URL=https://api.example.com/v1
|
|
15
|
+
LLM_ROUTER_TEST_EXAMPLE_CLAUDE_BASE_URL=https://api.example.com
|
|
16
|
+
LLM_ROUTER_TEST_EXAMPLE_MODELS=model-a,model-b
|
|
17
|
+
LLM_ROUTER_TEST_EXAMPLE_HEADERS_JSON={"X-Env":"local"}
|
|
18
|
+
LLM_ROUTER_TEST_EXAMPLE_OPENAI_HEADERS_JSON={"User-Agent":"Mozilla/5.0"}
|
|
19
|
+
LLM_ROUTER_TEST_EXAMPLE_CLAUDE_HEADERS_JSON={"x-foo":"bar"}
|
package/README.md
ADDED
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
# llm-router
|
|
2
|
+
|
|
3
|
+
`llm-router` is a gateway api proxy for accessing multiple models across any provider that supports OpenAI or Anthropic formats.
|
|
4
|
+
|
|
5
|
+
It supports:
|
|
6
|
+
- local route server `llm-router start`
|
|
7
|
+
- Cloudflare Worker route runtime deployment `llm-router deploy`
|
|
8
|
+
- CLI + TUI management `config`, `start`, `deploy`, `worker-key`
|
|
9
|
+
- Seamless model fallback
|
|
10
|
+
|
|
11
|
+
## Install
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
npm i -g @khanglvm/llm-router
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Quick Start
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
# 1) Open config TUI (default behavior) to manage providers, models, fallbacks, and auth
|
|
21
|
+
llm-router
|
|
22
|
+
|
|
23
|
+
# 2) Start local route server
|
|
24
|
+
llm-router start
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Local endpoints:
|
|
28
|
+
- Unified (Auto transform): `http://127.0.0.1:8787/route` (or `/` and `/v1`)
|
|
29
|
+
- Anthropic: `http://127.0.0.1:8787/anthropic`
|
|
30
|
+
- OpenAI: `http://127.0.0.1:8787/openai`
|
|
31
|
+
|
|
32
|
+
## Usage Example
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
# Your AI Agent can help! Ask them to manage api router via this tool for you.
|
|
36
|
+
|
|
37
|
+
# 1) Add provider + models + provider API key. You can ask your AI agent to do it for you, or manually via TUI or command line:
|
|
38
|
+
llm-router config \
|
|
39
|
+
--operation=upsert-provider \
|
|
40
|
+
--provider-id=openrouter \
|
|
41
|
+
--name="OpenRouter" \
|
|
42
|
+
--base-url=https://openrouter.ai/api/v1 \
|
|
43
|
+
--api-key=sk-or-v1-... \
|
|
44
|
+
--models=claude-3-7-sonnet,gpt-4o \
|
|
45
|
+
--format=openai \
|
|
46
|
+
--skip-probe=true
|
|
47
|
+
|
|
48
|
+
# 2) (Optional) Configure model fallback order
|
|
49
|
+
llm-router config \
|
|
50
|
+
--operation=set-model-fallbacks \
|
|
51
|
+
--provider-id=openrouter \
|
|
52
|
+
--model=claude-3-7-sonnet \
|
|
53
|
+
--fallback-models=openrouter/gpt-4o
|
|
54
|
+
|
|
55
|
+
# 3) Set master key (this is your gateway key for client apps)
|
|
56
|
+
llm-router config --operation=set-master-key --master-key=gw_your_gateway_key
|
|
57
|
+
|
|
58
|
+
# 4) Start gateway with auth required
|
|
59
|
+
llm-router start --require-auth=true
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
Claude Code example (`~/.claude/settings.local.json`):
|
|
63
|
+
|
|
64
|
+
```json
|
|
65
|
+
{
|
|
66
|
+
"env": {
|
|
67
|
+
"ANTHROPIC_BASE_URL": "http://127.0.0.1:8787/anthropic",
|
|
68
|
+
"ANTHROPIC_AUTH_TOKEN": "gw_your_gateway_key"
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## Smart Fallback Behavior
|
|
74
|
+
|
|
75
|
+
`llm-router` can fail over from a primary model to configured fallback models with status-aware logic:
|
|
76
|
+
- `429` (rate-limited): immediate fallback (no origin retry), with `Retry-After` respected when present.
|
|
77
|
+
- Temporary failures (`408`, `409`, `5xx`, network errors): origin-only bounded retries with jittered backoff, then fallback.
|
|
78
|
+
- Billing/quota exhaustion (`402`, or provider-specific billing signals): immediate fallback with longer origin cooldown memory.
|
|
79
|
+
- Auth and permission failures (`401` and relevant `403` cases): no retry; fallback to other providers/models when possible.
|
|
80
|
+
- Policy/moderation blocks: no retry; cross-provider fallback is disabled by default (`LLM_ROUTER_ALLOW_POLICY_FALLBACK=false`).
|
|
81
|
+
- Invalid client requests (`400`, `413`, `422`): no retry and no fallback short-circuit.
|
|
82
|
+
|
|
83
|
+
## Main Commands
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
llm-router config
|
|
87
|
+
llm-router start
|
|
88
|
+
llm-router stop
|
|
89
|
+
llm-router reload
|
|
90
|
+
llm-router update
|
|
91
|
+
llm-router deploy
|
|
92
|
+
llm-router worker-key
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
## Non-Interactive Config (Agent/CI Friendly)
|
|
96
|
+
|
|
97
|
+
```bash
|
|
98
|
+
llm-router config \
|
|
99
|
+
--operation=upsert-provider \
|
|
100
|
+
--provider-id=openrouter \
|
|
101
|
+
--name="OpenRouter" \
|
|
102
|
+
--base-url=https://openrouter.ai/api/v1 \
|
|
103
|
+
--api-key=sk-or-v1-... \
|
|
104
|
+
--models=gpt-4o,claude-3-7-sonnet \
|
|
105
|
+
--format=openai \
|
|
106
|
+
--skip-probe=true
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
Set local auth key:
|
|
110
|
+
|
|
111
|
+
```bash
|
|
112
|
+
llm-router config --operation=set-master-key --master-key=your_local_key
|
|
113
|
+
# or generate a strong key automatically
|
|
114
|
+
llm-router config --operation=set-master-key --generate-master-key=true
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
Start with auth required:
|
|
118
|
+
|
|
119
|
+
```bash
|
|
120
|
+
llm-router start --require-auth=true
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
## Cloudflare Worker Deploy
|
|
124
|
+
|
|
125
|
+
Worker project name in `wrangler.toml`: `llm-router-route`.
|
|
126
|
+
|
|
127
|
+
### Option A: Guided deploy
|
|
128
|
+
|
|
129
|
+
```bash
|
|
130
|
+
llm-router deploy
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
If `LLM_ROUTER_CONFIG_JSON` exceeds Cloudflare Free-tier secret size (`5 KB`), deploy now warns and requires explicit confirmation (default is `No`). In non-interactive environments, pass `--allow-large-config=true` to proceed intentionally.
|
|
134
|
+
|
|
135
|
+
`deploy` requires `CLOUDFLARE_API_TOKEN` for Cloudflare API access. Create a **User Profile API token** at <https://dash.cloudflare.com/profile/api-tokens> (do not use Account API Tokens), then choose preset/template `Edit Cloudflare Workers`. If the env var is missing in interactive mode, the CLI will show the guide and prompt for token input securely.
|
|
136
|
+
|
|
137
|
+
For multi-account tokens, set account explicitly in non-interactive runs:
|
|
138
|
+
- `CLOUDFLARE_ACCOUNT_ID=<id>` or
|
|
139
|
+
- `llm-router deploy --account-id=<id>`
|
|
140
|
+
|
|
141
|
+
`llm-router deploy` resolves deploy target from CLI/TUI input (workers.dev or custom route), generates a temporary Wrangler config at runtime, deploys with `--config`, then removes that temporary file. Personal route/account details are not persisted back into repo `wrangler.toml`.
|
|
142
|
+
|
|
143
|
+
For custom domains, the deploy helper now prints a DNS checklist and connectivity commands. Common setup for `llm.example.com`:
|
|
144
|
+
- Create a DNS record in Cloudflare for `llm` (usually `CNAME llm -> @`)
|
|
145
|
+
- Set **Proxy status = Proxied** (orange cloud)
|
|
146
|
+
- Use route target `--route-pattern=llm.example.com/* --zone-name=example.com`
|
|
147
|
+
- Claude Code base URL should be `https://llm.example.com/anthropic` (**no `:8787`**; that port is local-only)
|
|
148
|
+
|
|
149
|
+
```bash
|
|
150
|
+
llm-router deploy --export-only=true --out=.llm-router.worker.json
|
|
151
|
+
wrangler secret put LLM_ROUTER_CONFIG_JSON < .llm-router.worker.json
|
|
152
|
+
wrangler deploy
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
Rotate worker auth key quickly:
|
|
156
|
+
|
|
157
|
+
```bash
|
|
158
|
+
llm-router worker-key --master-key=new_key
|
|
159
|
+
# or generate and rotate immediately
|
|
160
|
+
llm-router worker-key --env=production --generate-master-key=true
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
If you intentionally need to bypass weak-key checks (not recommended), add `--allow-weak-master-key=true` to `deploy` or `worker-key`.
|
|
164
|
+
|
|
165
|
+
Cloudflare hardening and incident-response checklist: see [`SECURITY.md`](./SECURITY.md).
|
|
166
|
+
|
|
167
|
+
## Runtime Secrets / Env
|
|
168
|
+
|
|
169
|
+
Primary:
|
|
170
|
+
- `LLM_ROUTER_CONFIG_JSON`
|
|
171
|
+
- `LLM_ROUTER_MASTER_KEY` (optional override)
|
|
172
|
+
|
|
173
|
+
Also supported:
|
|
174
|
+
- `ROUTE_CONFIG_JSON`
|
|
175
|
+
- `LLM_ROUTER_JSON`
|
|
176
|
+
|
|
177
|
+
Optional resilience tuning:
|
|
178
|
+
- `LLM_ROUTER_ORIGIN_RETRY_ATTEMPTS` (default `3`)
|
|
179
|
+
- `LLM_ROUTER_ORIGIN_RETRY_BASE_DELAY_MS` (default `250`)
|
|
180
|
+
- `LLM_ROUTER_ORIGIN_RETRY_MAX_DELAY_MS` (default `3000`)
|
|
181
|
+
- `LLM_ROUTER_ORIGIN_FALLBACK_COOLDOWN_MS` (default `45000`)
|
|
182
|
+
- `LLM_ROUTER_ORIGIN_RATE_LIMIT_COOLDOWN_MS` (default `30000`)
|
|
183
|
+
- `LLM_ROUTER_ORIGIN_BILLING_COOLDOWN_MS` (default `900000`)
|
|
184
|
+
- `LLM_ROUTER_ORIGIN_AUTH_COOLDOWN_MS` (default `600000`)
|
|
185
|
+
- `LLM_ROUTER_ORIGIN_POLICY_COOLDOWN_MS` (default `120000`)
|
|
186
|
+
- `LLM_ROUTER_ALLOW_POLICY_FALLBACK` (default `false`)
|
|
187
|
+
- `LLM_ROUTER_FALLBACK_CIRCUIT_FAILURES` (default `2`)
|
|
188
|
+
- `LLM_ROUTER_FALLBACK_CIRCUIT_COOLDOWN_MS` (default `30000`)
|
|
189
|
+
- `LLM_ROUTER_MAX_REQUEST_BODY_BYTES` (default `1048576`, min `4096`, max `20971520`)
|
|
190
|
+
- `LLM_ROUTER_UPSTREAM_TIMEOUT_MS` (default `60000`, min `1000`, max `300000`)
|
|
191
|
+
|
|
192
|
+
Optional browser access (CORS):
|
|
193
|
+
- By default, cross-origin browser reads are denied unless explicitly allow-listed.
|
|
194
|
+
- `LLM_ROUTER_CORS_ALLOWED_ORIGINS` (comma-separated exact origins, e.g. `https://app.example.com`)
|
|
195
|
+
- `LLM_ROUTER_CORS_ALLOW_ALL=true` (allows any origin; not recommended for production)
|
|
196
|
+
|
|
197
|
+
Optional source IP allowlist (recommended for Worker deployments):
|
|
198
|
+
- `LLM_ROUTER_ALLOWED_IPS` (comma-separated client IPs; denies requests from all other IPs)
|
|
199
|
+
- `LLM_ROUTER_IP_ALLOWLIST` (alias of `LLM_ROUTER_ALLOWED_IPS`)
|
|
200
|
+
|
|
201
|
+
## Default Config Path
|
|
202
|
+
|
|
203
|
+
`~/.llm-router.json`
|
|
204
|
+
|
|
205
|
+
Minimal shape:
|
|
206
|
+
|
|
207
|
+
```json
|
|
208
|
+
{
|
|
209
|
+
"masterKey": "local_or_worker_key",
|
|
210
|
+
"defaultModel": "openrouter/gpt-4o",
|
|
211
|
+
"providers": [
|
|
212
|
+
{
|
|
213
|
+
"id": "openrouter",
|
|
214
|
+
"name": "OpenRouter",
|
|
215
|
+
"baseUrl": "https://openrouter.ai/api/v1",
|
|
216
|
+
"apiKey": "sk-or-v1-...",
|
|
217
|
+
"formats": ["openai"],
|
|
218
|
+
"models": [{ "id": "gpt-4o" }]
|
|
219
|
+
}
|
|
220
|
+
]
|
|
221
|
+
}
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
## Smoke Test
|
|
225
|
+
|
|
226
|
+
```bash
|
|
227
|
+
npm run test:provider-smoke
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
Use `.env.test-suite.example` as template for provider-based smoke tests.
|
package/package.json
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@khanglvm/llm-router",
|
|
3
|
+
"version": "1.0.5",
|
|
4
|
+
"description": "Single gateway endpoint for multi-provider LLMs with unified OpenAI+Anthropic format and seamless fallback",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "src/index.js",
|
|
7
|
+
"bin": {
|
|
8
|
+
"llm-router": "./src/cli-entry.js",
|
|
9
|
+
"llm-router-route": "./src/cli-entry.js"
|
|
10
|
+
},
|
|
11
|
+
"scripts": {
|
|
12
|
+
"dev": "wrangler dev",
|
|
13
|
+
"deploy": "wrangler deploy",
|
|
14
|
+
"tail": "wrangler tail",
|
|
15
|
+
"start": "node ./src/cli-entry.js start",
|
|
16
|
+
"config": "node ./src/cli-entry.js config",
|
|
17
|
+
"deploy:worker": "node ./src/cli-entry.js deploy",
|
|
18
|
+
"test:provider-smoke": "node ./scripts/provider-smoke-suite.mjs"
|
|
19
|
+
},
|
|
20
|
+
"dependencies": {
|
|
21
|
+
"@levu/snap": "^0.3.8"
|
|
22
|
+
},
|
|
23
|
+
"devDependencies": {
|
|
24
|
+
"wrangler": "^4.68.1"
|
|
25
|
+
}
|
|
26
|
+
}
|