@khanglvm/llm-router 2.2.4 → 2.2.6

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@khanglvm/llm-router",
3
- "version": "2.2.4",
3
+ "version": "2.2.6",
4
4
  "description": "LLM Router: single gateway endpoint for multi-provider LLMs with unified OpenAI+Anthropic format and seamless fallback",
5
5
  "keywords": [
6
6
  "llm-router",
@@ -8481,6 +8481,11 @@ async function runWebAction(context) {
8481
8481
  routerRequireAuth: toBoolean(readArg(args, ["router-require-auth", "routerRequireAuth"], false), false),
8482
8482
  allowRemoteClients: toBoolean(readArg(args, ["allow-remote-clients", "allowRemoteClients"], false), false),
8483
8483
  cliPathForRouter: process.argv[1],
8484
+ onPortConflict: canPrompt() && typeof context?.prompts?.confirm === "function"
8485
+ ? ({ port }) => context.prompts.confirm({
8486
+ message: `Port ${port} is already in use. Kill the existing listener and reclaim the port?`
8487
+ })
8488
+ : undefined,
8484
8489
  onLine: (line) => context.terminal.line(line),
8485
8490
  onError: (line) => context.terminal.error(line)
8486
8491
  });
@@ -1,6 +1,7 @@
1
1
  import { spawn } from "node:child_process";
2
2
  import { getDefaultConfigPath } from "./config-store.js";
3
3
  import { FIXED_LOCAL_ROUTER_HOST, FIXED_LOCAL_ROUTER_PORT } from "./local-server-settings.js";
4
+ import { reclaimPort } from "./port-reclaim.js";
4
5
  import { startWebConsoleServer } from "./web-console-server.js";
5
6
 
6
7
  function toBoolean(value, fallback) {
@@ -49,26 +50,76 @@ export async function runWebCommand(options = {}) {
49
50
  const line = typeof options.onLine === "function" ? options.onLine : console.log;
50
51
  const error = typeof options.onError === "function" ? options.onError : console.error;
51
52
 
53
+ const onPortConflict = typeof options.onPortConflict === "function" ? options.onPortConflict : null;
54
+ const reclaimPortFn = typeof options.reclaimPort === "function" ? options.reclaimPort : (args) => reclaimPort(args, options);
55
+
56
+ const buildServerOptions = () => ({
57
+ host,
58
+ port,
59
+ configPath,
60
+ routerHost: FIXED_LOCAL_ROUTER_HOST,
61
+ routerPort: FIXED_LOCAL_ROUTER_PORT,
62
+ routerWatchConfig: toBoolean(options.routerWatchConfig ?? options["router-watch-config"], true),
63
+ routerWatchBinary: toBoolean(options.routerWatchBinary ?? options["router-watch-binary"], true),
64
+ routerRequireAuth: toBoolean(options.routerRequireAuth ?? options["router-require-auth"], false),
65
+ allowRemoteClients: toBoolean(options.allowRemoteClients ?? options["allow-remote-clients"], false),
66
+ cliPathForRouter: String(options.cliPathForRouter || process.env.LLM_ROUTER_CLI_PATH || process.argv[1] || "").trim()
67
+ });
68
+
52
69
  let server;
53
70
  try {
54
- server = await startWebConsoleServer({
55
- host,
56
- port,
57
- configPath,
58
- routerHost: FIXED_LOCAL_ROUTER_HOST,
59
- routerPort: FIXED_LOCAL_ROUTER_PORT,
60
- routerWatchConfig: toBoolean(options.routerWatchConfig ?? options["router-watch-config"], true),
61
- routerWatchBinary: toBoolean(options.routerWatchBinary ?? options["router-watch-binary"], true),
62
- routerRequireAuth: toBoolean(options.routerRequireAuth ?? options["router-require-auth"], false),
63
- allowRemoteClients: toBoolean(options.allowRemoteClients ?? options["allow-remote-clients"], false),
64
- cliPathForRouter: String(options.cliPathForRouter || process.env.LLM_ROUTER_CLI_PATH || process.argv[1] || "").trim()
65
- });
71
+ server = await startWebConsoleServer(buildServerOptions());
66
72
  } catch (startError) {
67
- return {
68
- ok: false,
69
- exitCode: 1,
70
- errorMessage: `Failed to start the LLM Router web console: ${startError instanceof Error ? startError.message : String(startError)}`
71
- };
73
+ if (startError?.code !== "EADDRINUSE") {
74
+ return {
75
+ ok: false,
76
+ exitCode: 1,
77
+ errorMessage: `Failed to start the LLM Router web console: ${startError instanceof Error ? startError.message : String(startError)}`
78
+ };
79
+ }
80
+
81
+ if (!onPortConflict) {
82
+ return {
83
+ ok: false,
84
+ exitCode: 1,
85
+ errorMessage: `Port ${port} is already in use. Stop the existing listener or use a different port (--port=<number> or LLM_ROUTER_WEB_PORT env).`
86
+ };
87
+ }
88
+
89
+ let userChoice;
90
+ try {
91
+ userChoice = await onPortConflict({ port, host });
92
+ } catch {
93
+ userChoice = false;
94
+ }
95
+
96
+ if (!userChoice) {
97
+ return {
98
+ ok: true,
99
+ exitCode: 0,
100
+ data: `Port ${port} is in use. Web console launch cancelled.`
101
+ };
102
+ }
103
+
104
+ const reclaimed = await reclaimPortFn({ port, line, error });
105
+ if (!reclaimed.ok) {
106
+ return {
107
+ ok: false,
108
+ exitCode: 1,
109
+ errorMessage: reclaimed.errorMessage
110
+ };
111
+ }
112
+
113
+ try {
114
+ server = await startWebConsoleServer(buildServerOptions());
115
+ line(`Port ${port} reclaimed successfully.`);
116
+ } catch (retryError) {
117
+ return {
118
+ ok: false,
119
+ exitCode: 1,
120
+ errorMessage: `Failed to start the LLM Router web console after reclaiming port ${port}: ${retryError instanceof Error ? retryError.message : String(retryError)}`
121
+ };
122
+ }
72
123
  }
73
124
 
74
125
  line(`LLM Router web console started on ${server.url}`);