@khanglvm/llm-router 2.5.1 → 2.5.2
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 +5 -0
- package/README.md +2 -2
- package/package.json +1 -1
- package/src/node/dev-command.js +114 -0
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [2.5.2] - 2026-04-23
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
- `yarn dev` now force-reclaims stale dev web-console listeners on startup and restarts matching stale dev routers so the next dev session takes over the sandbox cleanly instead of inheriting the old process.
|
|
14
|
+
|
|
10
15
|
## [2.5.1] - 2026-04-23
|
|
11
16
|
|
|
12
17
|
### Fixed
|
package/README.md
CHANGED
|
@@ -29,7 +29,7 @@ llr ai-help # agent-oriented setup brief
|
|
|
29
29
|
- **Model aliases with routing** — group models into stable alias names with weighted round-robin, quota-aware balancing, and automatic fallback
|
|
30
30
|
- **Rate limiting** — set request caps per model or across all models over configurable time windows
|
|
31
31
|
- **Coding tool routing** — one-click routing config for Codex CLI, Claude Code, Factory Droid, and AMP
|
|
32
|
-
- **Dev sandbox** — `yarn dev` runs the console against a dedicated dev config/router port, highlights dev mode in terminal + UI,
|
|
32
|
+
- **Dev sandbox** — `yarn dev` runs the console against a dedicated dev config/router port, highlights dev mode in terminal + UI, can clone the production config into the sandbox for quick iteration, and automatically reclaims stale dev listeners before the next session starts
|
|
33
33
|
- **Claude native web tools** — local handling for Claude web search and page fetch requests, with selectable Claude Code web-search providers from the shared Web Search config
|
|
34
34
|
- **Seamless local updates** — `llr update` keeps the fixed local router endpoint online, drains in-flight requests, and automatically retries through backend restart windows
|
|
35
35
|
- **Web search** — built-in web search for AMP and other router-managed tools
|
|
@@ -60,7 +60,7 @@ That means `llr update` can install a new package version and gracefully swap th
|
|
|
60
60
|
yarn dev
|
|
61
61
|
```
|
|
62
62
|
|
|
63
|
-
Development mode uses the dedicated `~/.llm-router-dev.json` config and its own local router port so it can run alongside a startup-managed or manually started production router. The terminal and Web UI both show a dev-mode indicator,
|
|
63
|
+
Development mode uses the dedicated `~/.llm-router-dev.json` config and its own local router port so it can run alongside a startup-managed or manually started production router. The terminal and Web UI both show a dev-mode indicator, the dev Web UI includes a one-click sync action to copy the current production config into the sandbox without changing the dev router binding, and each new `yarn dev` run automatically takes over any stale dev web-console/router listeners from a prior session.
|
|
64
64
|
|
|
65
65
|
## Web UI
|
|
66
66
|
|
package/package.json
CHANGED
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { getActiveRuntimeState } from "./instance-state.js";
|
|
2
|
+
import { reclaimPort } from "./port-reclaim.js";
|
|
3
|
+
import { startWebConsoleServer } from "./web-console-server.js";
|
|
4
|
+
|
|
5
|
+
const DEV_ROUTER_STOP_REASON = "Stopping the dev router because the dev web console exited.";
|
|
6
|
+
|
|
7
|
+
function normalizeHost(value) {
|
|
8
|
+
return String(value || "127.0.0.1").trim() || "127.0.0.1";
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function shouldRestartStaleDevRouter(runtimeBeforeStart, runtimeAfterStart, snapshot) {
|
|
12
|
+
if (!runtimeBeforeStart || !runtimeAfterStart || !snapshot?.router?.running) return false;
|
|
13
|
+
if (Number(runtimeBeforeStart.pid) !== Number(runtimeAfterStart.pid)) return false;
|
|
14
|
+
if (snapshot?.config?.parseError) return false;
|
|
15
|
+
if (!Number(snapshot?.config?.providerCount)) return false;
|
|
16
|
+
|
|
17
|
+
const localServer = snapshot?.config?.localServer || {};
|
|
18
|
+
return Number(runtimeAfterStart.port) === Number(localServer.port)
|
|
19
|
+
&& normalizeHost(runtimeAfterStart.host) === normalizeHost(localServer.host);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
async function stopDevRouterAfterExit(server, onError) {
|
|
23
|
+
if (!server || typeof server.stopRouter !== "function") return;
|
|
24
|
+
|
|
25
|
+
try {
|
|
26
|
+
await server.stopRouter({
|
|
27
|
+
reason: DEV_ROUTER_STOP_REASON,
|
|
28
|
+
reclaimPortIfStopped: true
|
|
29
|
+
});
|
|
30
|
+
} catch (error) {
|
|
31
|
+
onError(`Failed stopping the dev router during shutdown: ${error instanceof Error ? error.message : String(error)}`);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export async function startManagedDevWebConsole(options = {}, deps = {}) {
|
|
36
|
+
const line = typeof deps.line === "function" ? deps.line : console.log;
|
|
37
|
+
const error = typeof deps.error === "function" ? deps.error : console.error;
|
|
38
|
+
const startWebConsoleServerFn = typeof deps.startWebConsoleServer === "function"
|
|
39
|
+
? deps.startWebConsoleServer
|
|
40
|
+
: startWebConsoleServer;
|
|
41
|
+
const getActiveRuntimeStateFn = typeof deps.getActiveRuntimeState === "function"
|
|
42
|
+
? deps.getActiveRuntimeState
|
|
43
|
+
: getActiveRuntimeState;
|
|
44
|
+
const reclaimPortFn = typeof deps.reclaimPort === "function"
|
|
45
|
+
? deps.reclaimPort
|
|
46
|
+
: (args) => reclaimPort(args, deps);
|
|
47
|
+
const serverOptions = {
|
|
48
|
+
...options,
|
|
49
|
+
devMode: true
|
|
50
|
+
};
|
|
51
|
+
const runtimeBeforeStart = await getActiveRuntimeStateFn().catch(() => null);
|
|
52
|
+
|
|
53
|
+
let server;
|
|
54
|
+
try {
|
|
55
|
+
server = await startWebConsoleServerFn(serverOptions);
|
|
56
|
+
} catch (startError) {
|
|
57
|
+
if (startError?.code !== "EADDRINUSE") throw startError;
|
|
58
|
+
|
|
59
|
+
const reclaimed = await reclaimPortFn({
|
|
60
|
+
port: serverOptions.port,
|
|
61
|
+
line,
|
|
62
|
+
error
|
|
63
|
+
});
|
|
64
|
+
if (!reclaimed?.ok) {
|
|
65
|
+
throw new Error(reclaimed?.errorMessage || `Failed to reclaim port ${serverOptions.port}.`);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
line(`Port ${serverOptions.port} reclaimed successfully.`);
|
|
69
|
+
server = await startWebConsoleServerFn(serverOptions);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const startupSnapshot = typeof server.getSnapshot === "function"
|
|
73
|
+
? await server.getSnapshot().catch(() => null)
|
|
74
|
+
: null;
|
|
75
|
+
const runtimeAfterStart = await getActiveRuntimeStateFn().catch(() => null);
|
|
76
|
+
if (shouldRestartStaleDevRouter(runtimeBeforeStart, runtimeAfterStart, startupSnapshot)
|
|
77
|
+
&& typeof server.restartRouter === "function") {
|
|
78
|
+
await server.restartRouter(startupSnapshot.config.localServer);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
let stopRouterPromise = null;
|
|
82
|
+
const ensureDevRouterStopped = () => {
|
|
83
|
+
if (stopRouterPromise) return stopRouterPromise;
|
|
84
|
+
stopRouterPromise = stopDevRouterAfterExit(server, error);
|
|
85
|
+
return stopRouterPromise;
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
const done = (async () => {
|
|
89
|
+
let result;
|
|
90
|
+
try {
|
|
91
|
+
result = await server.done;
|
|
92
|
+
} finally {
|
|
93
|
+
await ensureDevRouterStopped();
|
|
94
|
+
}
|
|
95
|
+
return result;
|
|
96
|
+
})();
|
|
97
|
+
|
|
98
|
+
let shutdownPromise = null;
|
|
99
|
+
const shutdown = async (reason = "dev-console-closed") => {
|
|
100
|
+
if (shutdownPromise) return shutdownPromise;
|
|
101
|
+
shutdownPromise = (async () => {
|
|
102
|
+
await ensureDevRouterStopped();
|
|
103
|
+
await server.close(reason);
|
|
104
|
+
return done;
|
|
105
|
+
})();
|
|
106
|
+
return shutdownPromise;
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
return {
|
|
110
|
+
...server,
|
|
111
|
+
done,
|
|
112
|
+
shutdown
|
|
113
|
+
};
|
|
114
|
+
}
|