@khanglvm/llm-router 2.3.6 → 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 +7 -0
- package/README.md +7 -0
- package/package.json +1 -1
- package/src/cli/router-module.js +1 -1
- package/src/cli-entry.js +17 -2
- package/src/node/coding-tool-config.js +179 -25
- 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 +1 -1
- package/src/node/web-console-server.js +72 -27
- package/src/shared/coding-tool-bindings.js +28 -7
- package/src/shared/local-router-defaults.js +15 -2
- package/src/shared/timeout-signal.js +6 -7
|
@@ -12,7 +12,6 @@ import {
|
|
|
12
12
|
areLocalServerSettingsEqual,
|
|
13
13
|
formatStartupDetail,
|
|
14
14
|
formatStartupLabel,
|
|
15
|
-
getFixedLocalRouterOrigin,
|
|
16
15
|
readLocalServerSettings
|
|
17
16
|
} from "./local-server-settings.js";
|
|
18
17
|
import { appendActivityLogEntry, clearActivityLogFile, createActivityLogEntry, readActivityLogEntries, resolveActivityLogPath } from "./activity-log.js";
|
|
@@ -444,8 +443,17 @@ function formatHostForUrl(host, port) {
|
|
|
444
443
|
return `[${value}]:${port}`;
|
|
445
444
|
}
|
|
446
445
|
|
|
446
|
+
function buildManagedRouterOrigin(settings = {}) {
|
|
447
|
+
const port = Number.isInteger(Number(settings?.port)) ? Number(settings.port) : FIXED_LOCAL_ROUTER_PORT;
|
|
448
|
+
const configuredHost = normalizeRuntimeHost(settings?.host || FIXED_LOCAL_ROUTER_HOST);
|
|
449
|
+
const host = isWildcardRuntimeHost(configuredHost) || isLoopbackRuntimeHost(configuredHost)
|
|
450
|
+
? FIXED_LOCAL_ROUTER_HOST
|
|
451
|
+
: configuredHost;
|
|
452
|
+
return `http://${formatHostForUrl(host, port)}`;
|
|
453
|
+
}
|
|
454
|
+
|
|
447
455
|
function buildAmpClientEndpointUrl(settings = {}) {
|
|
448
|
-
return
|
|
456
|
+
return buildManagedRouterOrigin(settings);
|
|
449
457
|
}
|
|
450
458
|
|
|
451
459
|
function buildCodexCliEndpointUrl(settings = {}) {
|
|
@@ -465,7 +473,7 @@ function buildFactoryDroidEndpointUrl(settings = {}) {
|
|
|
465
473
|
|
|
466
474
|
function buildRouterEndpoints({ host, port, running }) {
|
|
467
475
|
if (!running) return [];
|
|
468
|
-
const origin =
|
|
476
|
+
const origin = buildManagedRouterOrigin({ host, port });
|
|
469
477
|
return [
|
|
470
478
|
{ label: "Unified", url: `${origin}/route` },
|
|
471
479
|
{ label: "Anthropic", url: `${origin}/anthropic` },
|
|
@@ -805,8 +813,10 @@ function writeJsonLine(res, payload) {
|
|
|
805
813
|
|
|
806
814
|
function resolveRouterOptions(current, body) {
|
|
807
815
|
return {
|
|
808
|
-
host: FIXED_LOCAL_ROUTER_HOST,
|
|
809
|
-
port:
|
|
816
|
+
host: normalizeRuntimeHost(body?.host || current?.host || FIXED_LOCAL_ROUTER_HOST),
|
|
817
|
+
port: Number.isInteger(Number(body?.port))
|
|
818
|
+
? Number(body.port)
|
|
819
|
+
: (Number.isInteger(Number(current?.port)) ? Number(current.port) : FIXED_LOCAL_ROUTER_PORT),
|
|
810
820
|
watchConfig: body?.watchConfig === undefined ? current.watchConfig : body.watchConfig === true,
|
|
811
821
|
requireAuth: body?.requireAuth === undefined ? current.requireAuth : body.requireAuth === true,
|
|
812
822
|
watchBinary: body?.watchBinary === undefined ? current.watchBinary : body.watchBinary === true
|
|
@@ -815,8 +825,8 @@ function resolveRouterOptions(current, body) {
|
|
|
815
825
|
|
|
816
826
|
function getRouterStateSettings(routerState) {
|
|
817
827
|
return {
|
|
818
|
-
host: FIXED_LOCAL_ROUTER_HOST,
|
|
819
|
-
port: FIXED_LOCAL_ROUTER_PORT,
|
|
828
|
+
host: normalizeRuntimeHost(routerState?.host || FIXED_LOCAL_ROUTER_HOST),
|
|
829
|
+
port: Number.isInteger(Number(routerState?.port)) ? Number(routerState.port) : FIXED_LOCAL_ROUTER_PORT,
|
|
820
830
|
watchConfig: routerState?.watchConfig !== false,
|
|
821
831
|
watchBinary: routerState?.watchBinary !== false,
|
|
822
832
|
requireAuth: routerState?.requireAuth === true
|
|
@@ -882,6 +892,14 @@ export async function startWebConsoleServer(options = {}, deps = {}) {
|
|
|
882
892
|
: loadWebConsoleDevAssets;
|
|
883
893
|
const resolvedRouterCliPath = String(cliPathForRouter || process.env.LLM_ROUTER_CLI_PATH || process.argv[1] || "").trim();
|
|
884
894
|
const resolvedActivityLogPath = resolveActivityLogPath(configPath, activityLogPath);
|
|
895
|
+
const startupControlsEnabled = !devMode;
|
|
896
|
+
const defaultRouterSettings = {
|
|
897
|
+
host: normalizeRuntimeHost(routerHost || FIXED_LOCAL_ROUTER_HOST),
|
|
898
|
+
port: Number.isInteger(Number(routerPort)) ? Number(routerPort) : FIXED_LOCAL_ROUTER_PORT,
|
|
899
|
+
watchConfig: routerWatchConfig !== false,
|
|
900
|
+
watchBinary: routerWatchBinary !== false,
|
|
901
|
+
requireAuth: routerRequireAuth === true
|
|
902
|
+
};
|
|
885
903
|
|
|
886
904
|
async function readWebSearchState(config = null) {
|
|
887
905
|
if (!config || typeof config !== "object") return null;
|
|
@@ -1718,11 +1736,11 @@ export async function startWebConsoleServer(options = {}, deps = {}) {
|
|
|
1718
1736
|
let activityLogEnabled = true;
|
|
1719
1737
|
|
|
1720
1738
|
const routerState = {
|
|
1721
|
-
host:
|
|
1722
|
-
port:
|
|
1723
|
-
watchConfig:
|
|
1724
|
-
watchBinary:
|
|
1725
|
-
requireAuth:
|
|
1739
|
+
host: defaultRouterSettings.host,
|
|
1740
|
+
port: defaultRouterSettings.port,
|
|
1741
|
+
watchConfig: defaultRouterSettings.watchConfig,
|
|
1742
|
+
watchBinary: defaultRouterSettings.watchBinary,
|
|
1743
|
+
requireAuth: defaultRouterSettings.requireAuth,
|
|
1726
1744
|
lastError: ""
|
|
1727
1745
|
};
|
|
1728
1746
|
|
|
@@ -1987,6 +2005,7 @@ export async function startWebConsoleServer(options = {}, deps = {}) {
|
|
|
1987
2005
|
}
|
|
1988
2006
|
|
|
1989
2007
|
async function stopUntrackedStartupRuntime({ reason = "Stopped startup-managed LLM Router." } = {}) {
|
|
2008
|
+
if (!startupControlsEnabled) return false;
|
|
1990
2009
|
const startup = await startupStatusFn().catch(() => null);
|
|
1991
2010
|
if (!startup?.running) return false;
|
|
1992
2011
|
await stopStartupFn();
|
|
@@ -1996,6 +2015,12 @@ export async function startWebConsoleServer(options = {}, deps = {}) {
|
|
|
1996
2015
|
}
|
|
1997
2016
|
|
|
1998
2017
|
async function startStartupOwnedRouter(settings, { restart = false } = {}) {
|
|
2018
|
+
if (!startupControlsEnabled) {
|
|
2019
|
+
const error = new Error("Startup service controls are disabled in dev mode.");
|
|
2020
|
+
error.statusCode = 409;
|
|
2021
|
+
throw error;
|
|
2022
|
+
}
|
|
2023
|
+
|
|
1999
2024
|
await clearRuntimeStateFn();
|
|
2000
2025
|
const detail = await installStartupFn({
|
|
2001
2026
|
configPath,
|
|
@@ -2058,7 +2083,7 @@ export async function startWebConsoleServer(options = {}, deps = {}) {
|
|
|
2058
2083
|
};
|
|
2059
2084
|
}
|
|
2060
2085
|
|
|
2061
|
-
const startup = await startupStatusFn().catch(() => null);
|
|
2086
|
+
const startup = startupControlsEnabled ? await startupStatusFn().catch(() => null) : null;
|
|
2062
2087
|
const activeRuntime = await readManagedRuntime(configLocalServer);
|
|
2063
2088
|
if (activeRuntime) {
|
|
2064
2089
|
return {
|
|
@@ -2075,7 +2100,7 @@ export async function startWebConsoleServer(options = {}, deps = {}) {
|
|
|
2075
2100
|
await stopExternalRuntime(externalRuntime, {
|
|
2076
2101
|
reason: `Stopped an existing LLM Router instance so the web console can manage ${configLocalServer.host}:${configLocalServer.port} during ${reason}.`
|
|
2077
2102
|
});
|
|
2078
|
-
} else {
|
|
2103
|
+
} else if (startupControlsEnabled) {
|
|
2079
2104
|
await stopUntrackedStartupRuntime({
|
|
2080
2105
|
reason: `Stopped the startup-managed LLM Router instance so the web console can manage ${configLocalServer.host}:${configLocalServer.port} during ${reason}.`
|
|
2081
2106
|
});
|
|
@@ -2270,13 +2295,21 @@ export async function startWebConsoleServer(options = {}, deps = {}) {
|
|
|
2270
2295
|
const configState = await readConfigState(configPath);
|
|
2271
2296
|
const configLocalServer = getConfigLocalServer(configState);
|
|
2272
2297
|
const activityLog = resolveActivityLogSnapshot(configState.normalizedConfig);
|
|
2273
|
-
const startup =
|
|
2274
|
-
|
|
2275
|
-
|
|
2276
|
-
|
|
2277
|
-
|
|
2278
|
-
|
|
2279
|
-
|
|
2298
|
+
const startup = startupControlsEnabled
|
|
2299
|
+
? await startupStatusFn().catch((error) => ({
|
|
2300
|
+
manager: "unknown",
|
|
2301
|
+
serviceId: "llm-router",
|
|
2302
|
+
installed: false,
|
|
2303
|
+
running: false,
|
|
2304
|
+
detail: error instanceof Error ? error.message : String(error)
|
|
2305
|
+
}))
|
|
2306
|
+
: {
|
|
2307
|
+
manager: "disabled",
|
|
2308
|
+
serviceId: "llm-router",
|
|
2309
|
+
installed: false,
|
|
2310
|
+
running: false,
|
|
2311
|
+
detail: "Startup service controls are disabled in dev mode."
|
|
2312
|
+
};
|
|
2280
2313
|
const managedRuntime = await readManagedRuntime(configLocalServer);
|
|
2281
2314
|
const externalRuntime = managedRuntime ? null : await readExternalRuntime(configLocalServer);
|
|
2282
2315
|
const portProbe = probeRouterPort(configLocalServer);
|
|
@@ -2315,9 +2348,12 @@ export async function startWebConsoleServer(options = {}, deps = {}) {
|
|
|
2315
2348
|
router: routerSnapshot,
|
|
2316
2349
|
startup: {
|
|
2317
2350
|
...startup,
|
|
2318
|
-
label: formatStartupLabel(startup),
|
|
2319
|
-
friendlyDetail:
|
|
2320
|
-
|
|
2351
|
+
label: startupControlsEnabled ? formatStartupLabel(startup) : "Startup disabled in dev mode",
|
|
2352
|
+
friendlyDetail: startupControlsEnabled
|
|
2353
|
+
? formatStartupDetail(startup)
|
|
2354
|
+
: "Development web console will not install, stop, or replace startup-managed routers.",
|
|
2355
|
+
defaults: configLocalServer,
|
|
2356
|
+
available: startupControlsEnabled
|
|
2321
2357
|
},
|
|
2322
2358
|
ampClient: {
|
|
2323
2359
|
global: ampClientGlobal
|
|
@@ -2473,8 +2509,8 @@ export async function startWebConsoleServer(options = {}, deps = {}) {
|
|
|
2473
2509
|
nextOptions = persisted.savedSettings;
|
|
2474
2510
|
}
|
|
2475
2511
|
|
|
2476
|
-
const startup = await startupStatusFn().catch(() => null);
|
|
2477
|
-
const preferStartupOwnership = Boolean(startup?.installed);
|
|
2512
|
+
const startup = startupControlsEnabled ? await startupStatusFn().catch(() => null) : null;
|
|
2513
|
+
const preferStartupOwnership = startupControlsEnabled && Boolean(startup?.installed);
|
|
2478
2514
|
const runningRuntime = await readManagedRuntime(nextOptions);
|
|
2479
2515
|
const webConsoleConflict = getWebConsoleConflictMessage(nextOptions);
|
|
2480
2516
|
|
|
@@ -2501,7 +2537,7 @@ export async function startWebConsoleServer(options = {}, deps = {}) {
|
|
|
2501
2537
|
await stopExternalRuntime(externalRuntime, {
|
|
2502
2538
|
reason: "Stopped another LLM Router instance before starting the managed router."
|
|
2503
2539
|
});
|
|
2504
|
-
} else {
|
|
2540
|
+
} else if (startupControlsEnabled) {
|
|
2505
2541
|
await stopUntrackedStartupRuntime({
|
|
2506
2542
|
reason: "Stopped the startup-managed LLM Router instance before starting the managed router."
|
|
2507
2543
|
});
|
|
@@ -3733,6 +3769,10 @@ export async function startWebConsoleServer(options = {}, deps = {}) {
|
|
|
3733
3769
|
}
|
|
3734
3770
|
|
|
3735
3771
|
if (method === "POST" && requestUrl.pathname === "/api/startup/enable") {
|
|
3772
|
+
if (!startupControlsEnabled) {
|
|
3773
|
+
sendJson(res, 409, { error: "Startup service controls are unavailable in dev mode." });
|
|
3774
|
+
return;
|
|
3775
|
+
}
|
|
3736
3776
|
const body = await readJsonBody(req);
|
|
3737
3777
|
const configState = await readConfigState(configPath);
|
|
3738
3778
|
if (configState.parseError) {
|
|
@@ -3795,6 +3835,10 @@ export async function startWebConsoleServer(options = {}, deps = {}) {
|
|
|
3795
3835
|
}
|
|
3796
3836
|
|
|
3797
3837
|
if (method === "POST" && requestUrl.pathname === "/api/startup/disable") {
|
|
3838
|
+
if (!startupControlsEnabled) {
|
|
3839
|
+
sendJson(res, 409, { error: "Startup service controls are unavailable in dev mode." });
|
|
3840
|
+
return;
|
|
3841
|
+
}
|
|
3798
3842
|
await readJsonBody(req);
|
|
3799
3843
|
const statusBefore = await startupStatusFn().catch(() => null);
|
|
3800
3844
|
if (!statusBefore?.installed) {
|
|
@@ -4245,6 +4289,7 @@ export async function startWebConsoleServer(options = {}, deps = {}) {
|
|
|
4245
4289
|
|
|
4246
4290
|
addLog("info", `Web console listening on http://${formatHostForUrl(host, actualWebPort)}`);
|
|
4247
4291
|
if (devMode) addLog("info", "Development mode enabled for web assets.");
|
|
4292
|
+
if (!startupControlsEnabled) addLog("info", "Startup service controls disabled in dev mode.");
|
|
4248
4293
|
startConfigWatcher();
|
|
4249
4294
|
startActivityLogWatcher();
|
|
4250
4295
|
|
|
@@ -83,10 +83,31 @@ function sanitizeFactoryDroidRouterModelIdPart(value) {
|
|
|
83
83
|
function formatFactoryDroidDisplayNameBase(value) {
|
|
84
84
|
const normalized = String(value || "").trim();
|
|
85
85
|
if (!normalized) return "";
|
|
86
|
-
|
|
87
|
-
if (/^
|
|
88
|
-
if (/^
|
|
89
|
-
|
|
86
|
+
let next = normalized;
|
|
87
|
+
if (/^gpt(?=[-\s.]|$)/i.test(next)) next = `GPT${next.slice(3)}`;
|
|
88
|
+
else if (/^glm(?=[-\s.]|$)/i.test(next)) next = `GLM${next.slice(3)}`;
|
|
89
|
+
else if (/^claude(?=[-\s.]|$)/i.test(next)) next = `Claude${next.slice(6)}`;
|
|
90
|
+
|
|
91
|
+
return next
|
|
92
|
+
.replace(/(\d)-(\d)(?=(?:-|$))/g, "$1.$2")
|
|
93
|
+
.replace(/[_-]+/g, " ")
|
|
94
|
+
.replace(/\s+/g, " ")
|
|
95
|
+
.trim();
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function formatFactoryDroidProviderLabel(value) {
|
|
99
|
+
const normalized = String(value || "").trim();
|
|
100
|
+
if (!normalized) return "Provider";
|
|
101
|
+
if (normalized.toLowerCase() === "openrouter") return "OpenRouter";
|
|
102
|
+
if (normalized.toLowerCase() === "deepseek") return "DeepSeek";
|
|
103
|
+
if (/^[A-Za-z]{2,5}$/.test(normalized)) return normalized.toUpperCase();
|
|
104
|
+
return normalized
|
|
105
|
+
.replace(/[_-]+/g, " ")
|
|
106
|
+
.replace(/\s+/g, " ")
|
|
107
|
+
.split(" ")
|
|
108
|
+
.filter(Boolean)
|
|
109
|
+
.map((part) => part[0].toUpperCase() + part.slice(1))
|
|
110
|
+
.join(" ");
|
|
90
111
|
}
|
|
91
112
|
|
|
92
113
|
export function isFactoryDroidRouterModelId(value) {
|
|
@@ -181,20 +202,20 @@ export function buildFactoryDroidRouterModelId(modelRef, { kind = "" } = {}) {
|
|
|
181
202
|
return buildFactoryDroidRouterModelId(normalizedModelRef, { kind: "model" });
|
|
182
203
|
}
|
|
183
204
|
|
|
184
|
-
export function buildFactoryDroidRouterDisplayName(modelRef, { kind = "" } = {}) {
|
|
205
|
+
export function buildFactoryDroidRouterDisplayName(modelRef, { kind = "", providerName = "" } = {}) {
|
|
185
206
|
const normalizedModelRef = String(modelRef || "").trim();
|
|
186
207
|
if (!normalizedModelRef) return "";
|
|
187
208
|
|
|
188
209
|
const explicitKind = String(kind || "").trim().toLowerCase();
|
|
189
210
|
const inferredKind = explicitKind || (normalizedModelRef.includes("/") ? "model" : "alias");
|
|
190
211
|
if (inferredKind === "alias") {
|
|
191
|
-
return
|
|
212
|
+
return `${formatFactoryDroidDisplayNameBase(normalizedModelRef)} - LLM Router (Alias)`;
|
|
192
213
|
}
|
|
193
214
|
|
|
194
215
|
const modelName = normalizedModelRef.includes("/")
|
|
195
216
|
? normalizedModelRef.slice(normalizedModelRef.indexOf("/") + 1).trim()
|
|
196
217
|
: normalizedModelRef;
|
|
197
|
-
return
|
|
218
|
+
return `${formatFactoryDroidDisplayNameBase(modelName)} - LLM Router (${formatFactoryDroidProviderLabel(providerName)})`;
|
|
198
219
|
}
|
|
199
220
|
|
|
200
221
|
export function normalizeFactoryDroidReasoningEffort(value) {
|
|
@@ -20,16 +20,29 @@ function isPlainObject(value) {
|
|
|
20
20
|
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
+
function normalizeHost(value, fallback = LOCAL_ROUTER_HOST) {
|
|
24
|
+
const text = String(value || fallback).trim();
|
|
25
|
+
return text || fallback;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function normalizePort(value, fallback = LOCAL_ROUTER_PORT) {
|
|
29
|
+
const parsed = Number.parseInt(String(value ?? ""), 10);
|
|
30
|
+
if (!Number.isInteger(parsed) || parsed <= 0 || parsed > 65535) return fallback;
|
|
31
|
+
return parsed;
|
|
32
|
+
}
|
|
33
|
+
|
|
23
34
|
export function buildLocalRouterSettings(source = {}, fallback = {}) {
|
|
24
35
|
const base = {
|
|
36
|
+
host: normalizeHost(fallback?.host, LOCAL_ROUTER_HOST),
|
|
37
|
+
port: normalizePort(fallback?.port, LOCAL_ROUTER_PORT),
|
|
25
38
|
watchConfig: toBoolean(fallback?.watchConfig, true),
|
|
26
39
|
watchBinary: toBoolean(fallback?.watchBinary, true),
|
|
27
40
|
requireAuth: toBoolean(fallback?.requireAuth, false)
|
|
28
41
|
};
|
|
29
42
|
|
|
30
43
|
return {
|
|
31
|
-
host:
|
|
32
|
-
port:
|
|
44
|
+
host: normalizeHost(source?.host, base.host),
|
|
45
|
+
port: normalizePort(source?.port, base.port),
|
|
33
46
|
watchConfig: toBoolean(source?.watchConfig, base.watchConfig),
|
|
34
47
|
watchBinary: toBoolean(source?.watchBinary, base.watchBinary),
|
|
35
48
|
requireAuth: toBoolean(source?.requireAuth, base.requireAuth)
|
|
@@ -3,14 +3,13 @@ export function buildTimeoutSignal(timeoutMs) {
|
|
|
3
3
|
return { signal: undefined, cleanup: () => {} };
|
|
4
4
|
}
|
|
5
5
|
|
|
6
|
-
if (typeof AbortSignal !== "undefined" && typeof AbortSignal.timeout === "function") {
|
|
7
|
-
return {
|
|
8
|
-
signal: AbortSignal.timeout(timeoutMs),
|
|
9
|
-
cleanup: () => {}
|
|
10
|
-
};
|
|
11
|
-
}
|
|
12
|
-
|
|
13
6
|
if (typeof AbortController === "undefined") {
|
|
7
|
+
if (typeof AbortSignal !== "undefined" && typeof AbortSignal.timeout === "function") {
|
|
8
|
+
return {
|
|
9
|
+
signal: AbortSignal.timeout(timeoutMs),
|
|
10
|
+
cleanup: () => {}
|
|
11
|
+
};
|
|
12
|
+
}
|
|
14
13
|
return { signal: undefined, cleanup: () => {} };
|
|
15
14
|
}
|
|
16
15
|
|