@khanglvm/llm-router 2.3.5 → 2.3.7
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/CHANGELOG.md +12 -0
- package/README.md +8 -0
- package/package.json +1 -1
- package/src/cli/router-module.js +6 -3
- package/src/cli-entry.js +17 -2
- package/src/node/coding-tool-config.js +434 -41
- package/src/node/config-store.js +6 -1
- package/src/node/instance-state.js +4 -1
- package/src/node/local-server.js +40 -0
- package/src/node/router-supervisor.js +543 -0
- package/src/node/start-command.js +392 -61
- package/src/node/upgrade-command.js +90 -62
- package/src/node/web-console-client.js +20 -20
- package/src/node/web-console-server.js +84 -28
- package/src/shared/coding-tool-bindings.js +154 -0
- package/src/shared/local-router-defaults.js +15 -2
- package/src/shared/timeout-signal.js +6 -7
|
@@ -2,14 +2,10 @@ import { execSync } from "node:child_process";
|
|
|
2
2
|
import { readFileSync } from "node:fs";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { fileURLToPath } from "node:url";
|
|
5
|
-
import {
|
|
6
|
-
getActiveRuntimeState,
|
|
7
|
-
stopProcessByPid,
|
|
8
|
-
clearRuntimeState,
|
|
9
|
-
spawnDetachedStart
|
|
10
|
-
} from "./instance-state.js";
|
|
5
|
+
import { getActiveRuntimeState } from "./instance-state.js";
|
|
11
6
|
|
|
12
7
|
const PKG_NAME = "@khanglvm/llm-router";
|
|
8
|
+
const GRACEFUL_RESTART_SIGNAL = "SIGUSR2";
|
|
13
9
|
|
|
14
10
|
function readInstalledVersion() {
|
|
15
11
|
try {
|
|
@@ -21,41 +17,77 @@ function readInstalledVersion() {
|
|
|
21
17
|
}
|
|
22
18
|
}
|
|
23
19
|
|
|
24
|
-
function fetchLatestVersion() {
|
|
20
|
+
function fetchLatestVersion(exec = execSync) {
|
|
25
21
|
try {
|
|
26
|
-
return
|
|
22
|
+
return exec(`npm view ${PKG_NAME} version`, { encoding: "utf8" }).trim();
|
|
27
23
|
} catch {
|
|
28
24
|
return null;
|
|
29
25
|
}
|
|
30
26
|
}
|
|
31
27
|
|
|
32
|
-
function detectPackageManager() {
|
|
28
|
+
function detectPackageManager(exec = execSync) {
|
|
33
29
|
try {
|
|
34
|
-
const npmGlobalRoot =
|
|
30
|
+
const npmGlobalRoot = exec("npm root -g", { encoding: "utf8" }).trim();
|
|
35
31
|
const entryReal = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../..");
|
|
36
32
|
if (entryReal.startsWith(npmGlobalRoot)) return "npm";
|
|
37
|
-
} catch {
|
|
33
|
+
} catch {
|
|
34
|
+
// ignore
|
|
35
|
+
}
|
|
38
36
|
|
|
39
37
|
try {
|
|
40
|
-
const out =
|
|
38
|
+
const out = exec("pnpm list -g --json 2>/dev/null", { encoding: "utf8" });
|
|
41
39
|
if (out.includes(PKG_NAME)) return "pnpm";
|
|
42
|
-
} catch {
|
|
40
|
+
} catch {
|
|
41
|
+
// ignore
|
|
42
|
+
}
|
|
43
43
|
|
|
44
44
|
return "npm";
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
-
|
|
47
|
+
function requestGracefulRuntimeRestart(runtime, signalProcess = process.kill) {
|
|
48
|
+
const pid = Number(runtime?.pid);
|
|
49
|
+
if (!Number.isInteger(pid) || pid <= 0) {
|
|
50
|
+
return { ok: false, reason: "Invalid runtime pid." };
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
try {
|
|
54
|
+
signalProcess(pid, GRACEFUL_RESTART_SIGNAL);
|
|
55
|
+
return { ok: true, signal: GRACEFUL_RESTART_SIGNAL };
|
|
56
|
+
} catch (restartError) {
|
|
57
|
+
return {
|
|
58
|
+
ok: false,
|
|
59
|
+
reason: restartError instanceof Error ? restartError.message : String(restartError)
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export async function runUpgradeCommand({ onLine, onError } = {}, deps = {}) {
|
|
48
65
|
const line = typeof onLine === "function" ? onLine : (msg) => console.log(msg);
|
|
49
66
|
const error = typeof onError === "function" ? onError : (msg) => console.error(msg);
|
|
50
|
-
|
|
51
|
-
const
|
|
67
|
+
const exec = typeof deps.exec === "function" ? deps.exec : execSync;
|
|
68
|
+
const readInstalledVersionFn = typeof deps.readInstalledVersion === "function"
|
|
69
|
+
? deps.readInstalledVersion
|
|
70
|
+
: readInstalledVersion;
|
|
71
|
+
const fetchLatestVersionFn = typeof deps.fetchLatestVersion === "function"
|
|
72
|
+
? deps.fetchLatestVersion
|
|
73
|
+
: () => fetchLatestVersion(exec);
|
|
74
|
+
const detectPackageManagerFn = typeof deps.detectPackageManager === "function"
|
|
75
|
+
? deps.detectPackageManager
|
|
76
|
+
: () => detectPackageManager(exec);
|
|
77
|
+
const getActiveRuntimeStateFn = typeof deps.getActiveRuntimeState === "function"
|
|
78
|
+
? deps.getActiveRuntimeState
|
|
79
|
+
: getActiveRuntimeState;
|
|
80
|
+
const signalProcess = typeof deps.signalProcess === "function"
|
|
81
|
+
? deps.signalProcess
|
|
82
|
+
: process.kill;
|
|
83
|
+
|
|
84
|
+
const currentVersion = readInstalledVersionFn();
|
|
52
85
|
line(`Current version: ${currentVersion}`);
|
|
53
86
|
|
|
54
|
-
// Check latest
|
|
55
87
|
line("Checking for updates...");
|
|
56
|
-
const latestVersion =
|
|
88
|
+
const latestVersion = fetchLatestVersionFn();
|
|
57
89
|
if (!latestVersion) {
|
|
58
|
-
error("Could not fetch latest version from npm registry.");
|
|
90
|
+
error("Could not fetch the latest version from the npm registry.");
|
|
59
91
|
return { ok: false, exitCode: 1 };
|
|
60
92
|
}
|
|
61
93
|
|
|
@@ -64,65 +96,61 @@ export async function runUpgradeCommand({ onLine, onError } = {}) {
|
|
|
64
96
|
return { ok: true, exitCode: 0 };
|
|
65
97
|
}
|
|
66
98
|
|
|
67
|
-
line(`New version available: ${currentVersion}
|
|
99
|
+
line(`New version available: ${currentVersion} -> ${latestVersion}`);
|
|
68
100
|
|
|
69
|
-
|
|
70
|
-
let wasRunning = false;
|
|
71
|
-
let savedState = null;
|
|
101
|
+
let runtime = null;
|
|
72
102
|
try {
|
|
73
|
-
|
|
74
|
-
if (runtime) {
|
|
75
|
-
wasRunning = true;
|
|
76
|
-
savedState = { ...runtime };
|
|
77
|
-
line(`Stopping running server (pid ${runtime.pid})...`);
|
|
78
|
-
const stopResult = await stopProcessByPid(runtime.pid);
|
|
79
|
-
if (stopResult.ok) {
|
|
80
|
-
await clearRuntimeState({ pid: runtime.pid });
|
|
81
|
-
line("Server stopped.");
|
|
82
|
-
} else {
|
|
83
|
-
error(`Warning: could not stop server cleanly — ${stopResult.reason || "unknown"}`);
|
|
84
|
-
}
|
|
85
|
-
}
|
|
103
|
+
runtime = await getActiveRuntimeStateFn();
|
|
86
104
|
} catch {
|
|
87
|
-
|
|
105
|
+
runtime = null;
|
|
88
106
|
}
|
|
89
107
|
|
|
90
|
-
|
|
91
|
-
|
|
108
|
+
if (runtime) {
|
|
109
|
+
line(`Running router detected (pid ${runtime.pid}) at http://${runtime.host}:${runtime.port}.`);
|
|
110
|
+
line("The router will keep serving current requests while the package upgrade runs.");
|
|
111
|
+
} else {
|
|
112
|
+
line("No running local router detected. The new version will be used on the next start.");
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const pm = detectPackageManagerFn();
|
|
92
116
|
const installCmd = pm === "pnpm"
|
|
93
117
|
? `pnpm add -g ${PKG_NAME}@latest`
|
|
94
118
|
: `npm install -g ${PKG_NAME}@latest`;
|
|
95
119
|
|
|
96
120
|
line(`Upgrading via: ${installCmd}`);
|
|
97
121
|
try {
|
|
98
|
-
|
|
122
|
+
exec(installCmd, { stdio: "inherit" });
|
|
99
123
|
} catch {
|
|
100
|
-
error("Upgrade failed. You may need
|
|
124
|
+
error("Upgrade failed. The running router was left untouched. You may need sudo or corrected npm permissions.");
|
|
101
125
|
return { ok: false, exitCode: 1 };
|
|
102
126
|
}
|
|
103
127
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
error(`Could not restart server: ${err instanceof Error ? err.message : String(err)}`);
|
|
123
|
-
line("Start manually with: llr start");
|
|
124
|
-
}
|
|
128
|
+
line(`Upgraded package to ${latestVersion}.`);
|
|
129
|
+
|
|
130
|
+
if (!runtime) {
|
|
131
|
+
return { ok: true, exitCode: 0 };
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const restartResult = requestGracefulRuntimeRestart(runtime, signalProcess);
|
|
135
|
+
if (restartResult.ok) {
|
|
136
|
+
line(`Requested a graceful router reload (pid ${runtime.pid}).`);
|
|
137
|
+
line("Existing requests will drain before the new version takes over.");
|
|
138
|
+
return { ok: true, exitCode: 0 };
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
error(`Could not trigger an immediate graceful router reload: ${restartResult.reason || "unknown error"}`);
|
|
142
|
+
if (runtime.watchBinary) {
|
|
143
|
+
line("The running router is still serving traffic and should self-reload when its binary watcher detects the new version.");
|
|
144
|
+
} else {
|
|
145
|
+
line("Binary update watch is disabled for the running router. Restart it manually with `llr start` to activate the new version.");
|
|
125
146
|
}
|
|
126
147
|
|
|
127
148
|
return { ok: true, exitCode: 0 };
|
|
128
149
|
}
|
|
150
|
+
|
|
151
|
+
export {
|
|
152
|
+
detectPackageManager,
|
|
153
|
+
fetchLatestVersion,
|
|
154
|
+
readInstalledVersion,
|
|
155
|
+
requestGracefulRuntimeRestart
|
|
156
|
+
};
|