@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,369 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OS startup integration for llm-router.
|
|
3
|
+
* Supports macOS LaunchAgent and Linux systemd --user service.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import os from "node:os";
|
|
7
|
+
import path from "node:path";
|
|
8
|
+
import { promises as fs, existsSync } from "node:fs";
|
|
9
|
+
import { spawnSync } from "node:child_process";
|
|
10
|
+
|
|
11
|
+
const SERVICE_NAME = "llm-router";
|
|
12
|
+
const LAUNCH_AGENT_ID = "dev.llm-router";
|
|
13
|
+
|
|
14
|
+
function resolveDarwinDomain() {
|
|
15
|
+
const uid = process.getuid?.();
|
|
16
|
+
return uid !== undefined ? `gui/${uid}` : "gui/$(id -u)";
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function quoteArg(value) {
|
|
20
|
+
const escaped = String(value).replace(/"/g, '\\"');
|
|
21
|
+
return `"${escaped}"`;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function runCommand(command, args, { cwd } = {}) {
|
|
25
|
+
const result = spawnSync(command, args, {
|
|
26
|
+
cwd,
|
|
27
|
+
encoding: "utf8",
|
|
28
|
+
env: {
|
|
29
|
+
...process.env,
|
|
30
|
+
FORCE_COLOR: "0"
|
|
31
|
+
}
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
return {
|
|
35
|
+
ok: result.status === 0,
|
|
36
|
+
status: result.status ?? 1,
|
|
37
|
+
stdout: result.stdout || "",
|
|
38
|
+
stderr: result.stderr || "",
|
|
39
|
+
error: result.error
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function resolveCliEntryPath() {
|
|
44
|
+
if (process.env.LLM_ROUTER_CLI_PATH) return process.env.LLM_ROUTER_CLI_PATH;
|
|
45
|
+
const nodeBinDir = path.dirname(process.execPath);
|
|
46
|
+
for (const binName of ["llm-router", "llm-router-route"]) {
|
|
47
|
+
const candidate = path.join(nodeBinDir, binName);
|
|
48
|
+
if (existsSync(candidate)) return candidate;
|
|
49
|
+
}
|
|
50
|
+
if (process.argv[1]) return path.resolve(process.argv[1]);
|
|
51
|
+
throw new Error("Unable to resolve llm-router CLI entry path.");
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function makeExecArgs({ configPath, host, port, watchConfig, watchBinary, requireAuth }) {
|
|
55
|
+
return [
|
|
56
|
+
"start",
|
|
57
|
+
`--config=${configPath}`,
|
|
58
|
+
`--host=${host}`,
|
|
59
|
+
`--port=${port}`,
|
|
60
|
+
`--watch-config=${watchConfig ? "true" : "false"}`,
|
|
61
|
+
`--watch-binary=${watchBinary ? "true" : "false"}`,
|
|
62
|
+
`--require-auth=${requireAuth ? "true" : "false"}`
|
|
63
|
+
];
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function buildLaunchAgentPlist({ nodePath, cliPath, configPath, host, port, watchConfig, watchBinary, requireAuth }) {
|
|
67
|
+
const logDir = path.join(os.homedir(), "Library", "Logs");
|
|
68
|
+
const stdoutPath = path.join(logDir, "llm-router.out.log");
|
|
69
|
+
const stderrPath = path.join(logDir, "llm-router.err.log");
|
|
70
|
+
const args = [nodePath, cliPath, ...makeExecArgs({ configPath, host, port, watchConfig, watchBinary, requireAuth })];
|
|
71
|
+
|
|
72
|
+
const xmlArgs = args.map((arg) => ` <string>${arg}</string>`).join("\n");
|
|
73
|
+
|
|
74
|
+
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
75
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
76
|
+
<plist version="1.0">
|
|
77
|
+
<dict>
|
|
78
|
+
<key>Label</key>
|
|
79
|
+
<string>${LAUNCH_AGENT_ID}</string>
|
|
80
|
+
<key>ProgramArguments</key>
|
|
81
|
+
<array>
|
|
82
|
+
${xmlArgs}
|
|
83
|
+
</array>
|
|
84
|
+
<key>RunAtLoad</key>
|
|
85
|
+
<true/>
|
|
86
|
+
<key>KeepAlive</key>
|
|
87
|
+
<true/>
|
|
88
|
+
<key>EnvironmentVariables</key>
|
|
89
|
+
<dict>
|
|
90
|
+
<key>LLM_ROUTER_MANAGED_BY_STARTUP</key>
|
|
91
|
+
<string>1</string>
|
|
92
|
+
<key>LLM_ROUTER_CLI_PATH</key>
|
|
93
|
+
<string>${cliPath}</string>
|
|
94
|
+
</dict>
|
|
95
|
+
<key>StandardOutPath</key>
|
|
96
|
+
<string>${stdoutPath}</string>
|
|
97
|
+
<key>StandardErrorPath</key>
|
|
98
|
+
<string>${stderrPath}</string>
|
|
99
|
+
<key>WorkingDirectory</key>
|
|
100
|
+
<string>${process.cwd()}</string>
|
|
101
|
+
</dict>
|
|
102
|
+
</plist>
|
|
103
|
+
`;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function buildSystemdService({ nodePath, cliPath, configPath, host, port, watchConfig, watchBinary, requireAuth }) {
|
|
107
|
+
const execArgs = makeExecArgs({ configPath, host, port, watchConfig, watchBinary, requireAuth }).map(quoteArg).join(" ");
|
|
108
|
+
const execStart = `${quoteArg(nodePath)} ${quoteArg(cliPath)} ${execArgs}`;
|
|
109
|
+
|
|
110
|
+
return `[Unit]
|
|
111
|
+
Description=LLM Router local route
|
|
112
|
+
After=network.target
|
|
113
|
+
|
|
114
|
+
[Service]
|
|
115
|
+
Type=simple
|
|
116
|
+
ExecStart=${execStart}
|
|
117
|
+
Restart=always
|
|
118
|
+
RestartSec=2
|
|
119
|
+
Environment=NODE_ENV=production
|
|
120
|
+
Environment=LLM_ROUTER_MANAGED_BY_STARTUP=1
|
|
121
|
+
Environment=LLM_ROUTER_CLI_PATH=${cliPath}
|
|
122
|
+
WorkingDirectory=${process.cwd()}
|
|
123
|
+
|
|
124
|
+
[Install]
|
|
125
|
+
WantedBy=default.target
|
|
126
|
+
`;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
async function installDarwin({ configPath, host, port, watchConfig, watchBinary, requireAuth }) {
|
|
130
|
+
const launchAgentsDir = path.join(os.homedir(), "Library", "LaunchAgents");
|
|
131
|
+
const plistPath = path.join(launchAgentsDir, `${LAUNCH_AGENT_ID}.plist`);
|
|
132
|
+
const nodePath = process.execPath;
|
|
133
|
+
const cliPath = resolveCliEntryPath();
|
|
134
|
+
|
|
135
|
+
await fs.mkdir(launchAgentsDir, { recursive: true });
|
|
136
|
+
await fs.mkdir(path.join(os.homedir(), "Library", "Logs"), { recursive: true });
|
|
137
|
+
|
|
138
|
+
const content = buildLaunchAgentPlist({
|
|
139
|
+
nodePath,
|
|
140
|
+
cliPath,
|
|
141
|
+
configPath,
|
|
142
|
+
host,
|
|
143
|
+
port,
|
|
144
|
+
watchConfig,
|
|
145
|
+
watchBinary,
|
|
146
|
+
requireAuth
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
await fs.writeFile(plistPath, content, "utf8");
|
|
150
|
+
|
|
151
|
+
const domain = resolveDarwinDomain();
|
|
152
|
+
|
|
153
|
+
// Best effort reload sequence.
|
|
154
|
+
runCommand("launchctl", ["bootout", domain, plistPath]);
|
|
155
|
+
const bootstrap = runCommand("launchctl", ["bootstrap", domain, plistPath]);
|
|
156
|
+
if (!bootstrap.ok) {
|
|
157
|
+
throw new Error(bootstrap.stderr || bootstrap.stdout || "launchctl bootstrap failed.");
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
runCommand("launchctl", ["enable", `${domain}/${LAUNCH_AGENT_ID}`]);
|
|
161
|
+
|
|
162
|
+
return {
|
|
163
|
+
manager: "launchd",
|
|
164
|
+
serviceId: LAUNCH_AGENT_ID,
|
|
165
|
+
filePath: plistPath
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
async function uninstallDarwin() {
|
|
170
|
+
const plistPath = path.join(os.homedir(), "Library", "LaunchAgents", `${LAUNCH_AGENT_ID}.plist`);
|
|
171
|
+
const domain = resolveDarwinDomain();
|
|
172
|
+
|
|
173
|
+
runCommand("launchctl", ["bootout", domain, plistPath]);
|
|
174
|
+
|
|
175
|
+
try {
|
|
176
|
+
await fs.unlink(plistPath);
|
|
177
|
+
} catch (error) {
|
|
178
|
+
if (!(error && typeof error === "object" && error.code === "ENOENT")) {
|
|
179
|
+
throw error;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
return {
|
|
184
|
+
manager: "launchd",
|
|
185
|
+
serviceId: LAUNCH_AGENT_ID,
|
|
186
|
+
filePath: plistPath
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
async function statusDarwin() {
|
|
191
|
+
const plistPath = path.join(os.homedir(), "Library", "LaunchAgents", `${LAUNCH_AGENT_ID}.plist`);
|
|
192
|
+
let installed = true;
|
|
193
|
+
try {
|
|
194
|
+
await fs.access(plistPath);
|
|
195
|
+
} catch {
|
|
196
|
+
installed = false;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
const domain = resolveDarwinDomain();
|
|
200
|
+
const listResult = runCommand("launchctl", ["print", `${domain}/${LAUNCH_AGENT_ID}`]);
|
|
201
|
+
|
|
202
|
+
return {
|
|
203
|
+
manager: "launchd",
|
|
204
|
+
serviceId: LAUNCH_AGENT_ID,
|
|
205
|
+
installed,
|
|
206
|
+
running: listResult.ok,
|
|
207
|
+
filePath: plistPath,
|
|
208
|
+
detail: listResult.ok ? listResult.stdout : (listResult.stderr || listResult.stdout)
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
async function stopDarwin() {
|
|
213
|
+
const plistPath = path.join(os.homedir(), "Library", "LaunchAgents", `${LAUNCH_AGENT_ID}.plist`);
|
|
214
|
+
const domain = resolveDarwinDomain();
|
|
215
|
+
runCommand("launchctl", ["bootout", domain, plistPath]);
|
|
216
|
+
return statusDarwin();
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
async function restartDarwin() {
|
|
220
|
+
const plistPath = path.join(os.homedir(), "Library", "LaunchAgents", `${LAUNCH_AGENT_ID}.plist`);
|
|
221
|
+
await fs.access(plistPath);
|
|
222
|
+
const domain = resolveDarwinDomain();
|
|
223
|
+
runCommand("launchctl", ["bootout", domain, plistPath]);
|
|
224
|
+
const bootstrap = runCommand("launchctl", ["bootstrap", domain, plistPath]);
|
|
225
|
+
if (!bootstrap.ok) {
|
|
226
|
+
throw new Error(bootstrap.stderr || bootstrap.stdout || "launchctl bootstrap failed.");
|
|
227
|
+
}
|
|
228
|
+
runCommand("launchctl", ["enable", `${domain}/${LAUNCH_AGENT_ID}`]);
|
|
229
|
+
return statusDarwin();
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
async function installLinux({ configPath, host, port, watchConfig, watchBinary, requireAuth }) {
|
|
233
|
+
const systemdDir = path.join(os.homedir(), ".config", "systemd", "user");
|
|
234
|
+
const servicePath = path.join(systemdDir, `${SERVICE_NAME}.service`);
|
|
235
|
+
const nodePath = process.execPath;
|
|
236
|
+
const cliPath = resolveCliEntryPath();
|
|
237
|
+
|
|
238
|
+
await fs.mkdir(systemdDir, { recursive: true });
|
|
239
|
+
const content = buildSystemdService({
|
|
240
|
+
nodePath,
|
|
241
|
+
cliPath,
|
|
242
|
+
configPath,
|
|
243
|
+
host,
|
|
244
|
+
port,
|
|
245
|
+
watchConfig,
|
|
246
|
+
watchBinary,
|
|
247
|
+
requireAuth
|
|
248
|
+
});
|
|
249
|
+
await fs.writeFile(servicePath, content, "utf8");
|
|
250
|
+
|
|
251
|
+
const daemonReload = runCommand("systemctl", ["--user", "daemon-reload"]);
|
|
252
|
+
if (!daemonReload.ok) {
|
|
253
|
+
throw new Error(daemonReload.stderr || daemonReload.stdout || "systemctl daemon-reload failed.");
|
|
254
|
+
}
|
|
255
|
+
const enableNow = runCommand("systemctl", ["--user", "enable", "--now", `${SERVICE_NAME}.service`]);
|
|
256
|
+
if (!enableNow.ok) {
|
|
257
|
+
throw new Error(enableNow.stderr || enableNow.stdout || "systemctl enable --now failed.");
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
return {
|
|
261
|
+
manager: "systemd-user",
|
|
262
|
+
serviceId: `${SERVICE_NAME}.service`,
|
|
263
|
+
filePath: servicePath
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
async function uninstallLinux() {
|
|
268
|
+
const servicePath = path.join(os.homedir(), ".config", "systemd", "user", `${SERVICE_NAME}.service`);
|
|
269
|
+
runCommand("systemctl", ["--user", "disable", "--now", `${SERVICE_NAME}.service`]);
|
|
270
|
+
runCommand("systemctl", ["--user", "daemon-reload"]);
|
|
271
|
+
|
|
272
|
+
try {
|
|
273
|
+
await fs.unlink(servicePath);
|
|
274
|
+
} catch (error) {
|
|
275
|
+
if (!(error && typeof error === "object" && error.code === "ENOENT")) {
|
|
276
|
+
throw error;
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
return {
|
|
281
|
+
manager: "systemd-user",
|
|
282
|
+
serviceId: `${SERVICE_NAME}.service`,
|
|
283
|
+
filePath: servicePath
|
|
284
|
+
};
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
async function statusLinux() {
|
|
288
|
+
const servicePath = path.join(os.homedir(), ".config", "systemd", "user", `${SERVICE_NAME}.service`);
|
|
289
|
+
let installed = true;
|
|
290
|
+
try {
|
|
291
|
+
await fs.access(servicePath);
|
|
292
|
+
} catch {
|
|
293
|
+
installed = false;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
const isActive = runCommand("systemctl", ["--user", "is-active", `${SERVICE_NAME}.service`]);
|
|
297
|
+
return {
|
|
298
|
+
manager: "systemd-user",
|
|
299
|
+
serviceId: `${SERVICE_NAME}.service`,
|
|
300
|
+
installed,
|
|
301
|
+
running: isActive.ok && isActive.stdout.trim() === "active",
|
|
302
|
+
filePath: servicePath,
|
|
303
|
+
detail: isActive.stdout || isActive.stderr
|
|
304
|
+
};
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
async function stopLinux() {
|
|
308
|
+
runCommand("systemctl", ["--user", "stop", `${SERVICE_NAME}.service`]);
|
|
309
|
+
return statusLinux();
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
async function restartLinux() {
|
|
313
|
+
const servicePath = path.join(os.homedir(), ".config", "systemd", "user", `${SERVICE_NAME}.service`);
|
|
314
|
+
await fs.access(servicePath);
|
|
315
|
+
const restart = runCommand("systemctl", ["--user", "restart", `${SERVICE_NAME}.service`]);
|
|
316
|
+
if (!restart.ok) {
|
|
317
|
+
const start = runCommand("systemctl", ["--user", "start", `${SERVICE_NAME}.service`]);
|
|
318
|
+
if (!start.ok) {
|
|
319
|
+
throw new Error(start.stderr || start.stdout || restart.stderr || restart.stdout || "systemctl restart failed.");
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
return statusLinux();
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
export async function installStartup(options) {
|
|
326
|
+
const payload = {
|
|
327
|
+
configPath: options.configPath,
|
|
328
|
+
host: options.host || "127.0.0.1",
|
|
329
|
+
port: options.port || 8787,
|
|
330
|
+
watchConfig: options.watchConfig !== false,
|
|
331
|
+
watchBinary: options.watchBinary !== false,
|
|
332
|
+
requireAuth: options.requireAuth === true
|
|
333
|
+
};
|
|
334
|
+
|
|
335
|
+
if (process.platform === "darwin") return installDarwin(payload);
|
|
336
|
+
if (process.platform === "linux") return installLinux(payload);
|
|
337
|
+
|
|
338
|
+
throw new Error(`OS startup is not supported on platform '${process.platform}' yet.`);
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
export async function uninstallStartup() {
|
|
342
|
+
if (process.platform === "darwin") return uninstallDarwin();
|
|
343
|
+
if (process.platform === "linux") return uninstallLinux();
|
|
344
|
+
throw new Error(`OS startup is not supported on platform '${process.platform}' yet.`);
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
export async function startupStatus() {
|
|
348
|
+
if (process.platform === "darwin") return statusDarwin();
|
|
349
|
+
if (process.platform === "linux") return statusLinux();
|
|
350
|
+
return {
|
|
351
|
+
manager: "unsupported",
|
|
352
|
+
serviceId: SERVICE_NAME,
|
|
353
|
+
installed: false,
|
|
354
|
+
running: false,
|
|
355
|
+
detail: `Platform '${process.platform}' is not supported yet.`
|
|
356
|
+
};
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
export async function stopStartup() {
|
|
360
|
+
if (process.platform === "darwin") return stopDarwin();
|
|
361
|
+
if (process.platform === "linux") return stopLinux();
|
|
362
|
+
throw new Error(`OS startup is not supported on platform '${process.platform}' yet.`);
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
export async function restartStartup() {
|
|
366
|
+
if (process.platform === "darwin") return restartDarwin();
|
|
367
|
+
if (process.platform === "linux") return restartLinux();
|
|
368
|
+
throw new Error(`OS startup is not supported on platform '${process.platform}' yet.`);
|
|
369
|
+
}
|