@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.
@@ -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 getFixedLocalRouterOrigin();
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 = getFixedLocalRouterOrigin();
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: FIXED_LOCAL_ROUTER_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: FIXED_LOCAL_ROUTER_HOST,
1722
- port: FIXED_LOCAL_ROUTER_PORT,
1723
- watchConfig: routerWatchConfig,
1724
- watchBinary: routerWatchBinary,
1725
- requireAuth: routerRequireAuth,
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 = await startupStatusFn().catch((error) => ({
2274
- manager: "unknown",
2275
- serviceId: "llm-router",
2276
- installed: false,
2277
- running: false,
2278
- detail: error instanceof Error ? error.message : String(error)
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: formatStartupDetail(startup),
2320
- defaults: configLocalServer
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
- if (/^gpt(?=[-\s.]|$)/i.test(normalized)) return `GPT${normalized.slice(3)}`;
87
- if (/^glm(?=[-\s.]|$)/i.test(normalized)) return `GLM${normalized.slice(3)}`;
88
- if (/^claude(?=[-\s.]|$)/i.test(normalized)) return `Claude${normalized.slice(6)}`;
89
- return normalized;
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 `[LLM Alias] ${formatFactoryDroidDisplayNameBase(normalizedModelRef)}`;
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 `[LLM] ${formatFactoryDroidDisplayNameBase(modelName)}`;
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: LOCAL_ROUTER_HOST,
32
- port: LOCAL_ROUTER_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