@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
|
@@ -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;
|
|
@@ -1488,7 +1506,8 @@ export async function startWebConsoleServer(options = {}, deps = {}) {
|
|
|
1488
1506
|
const endpointUrl = buildAmpClientEndpointUrl(settings);
|
|
1489
1507
|
try {
|
|
1490
1508
|
const state = await readFactoryDroidRoutingState({
|
|
1491
|
-
endpointUrl
|
|
1509
|
+
endpointUrl,
|
|
1510
|
+
config
|
|
1492
1511
|
});
|
|
1493
1512
|
return {
|
|
1494
1513
|
...state,
|
|
@@ -1512,6 +1531,13 @@ export async function startWebConsoleServer(options = {}, deps = {}) {
|
|
|
1512
1531
|
missionValidatorModel: "",
|
|
1513
1532
|
reasoningEffort: ""
|
|
1514
1533
|
},
|
|
1534
|
+
bindingIds: {
|
|
1535
|
+
defaultModel: "",
|
|
1536
|
+
missionOrchestratorModel: "",
|
|
1537
|
+
missionWorkerModel: "",
|
|
1538
|
+
missionValidatorModel: "",
|
|
1539
|
+
reasoningEffort: ""
|
|
1540
|
+
},
|
|
1515
1541
|
endpointUrl,
|
|
1516
1542
|
error: error instanceof Error ? error.message : String(error)
|
|
1517
1543
|
};
|
|
@@ -1551,6 +1577,7 @@ export async function startWebConsoleServer(options = {}, deps = {}) {
|
|
|
1551
1577
|
endpointUrl: nextEndpointUrl,
|
|
1552
1578
|
apiKey: nextMasterKey,
|
|
1553
1579
|
bindings,
|
|
1580
|
+
config: nextConfig,
|
|
1554
1581
|
captureBackup: false
|
|
1555
1582
|
});
|
|
1556
1583
|
if (endpointOrKeyChanged) {
|
|
@@ -1709,11 +1736,11 @@ export async function startWebConsoleServer(options = {}, deps = {}) {
|
|
|
1709
1736
|
let activityLogEnabled = true;
|
|
1710
1737
|
|
|
1711
1738
|
const routerState = {
|
|
1712
|
-
host:
|
|
1713
|
-
port:
|
|
1714
|
-
watchConfig:
|
|
1715
|
-
watchBinary:
|
|
1716
|
-
requireAuth:
|
|
1739
|
+
host: defaultRouterSettings.host,
|
|
1740
|
+
port: defaultRouterSettings.port,
|
|
1741
|
+
watchConfig: defaultRouterSettings.watchConfig,
|
|
1742
|
+
watchBinary: defaultRouterSettings.watchBinary,
|
|
1743
|
+
requireAuth: defaultRouterSettings.requireAuth,
|
|
1717
1744
|
lastError: ""
|
|
1718
1745
|
};
|
|
1719
1746
|
|
|
@@ -1978,6 +2005,7 @@ export async function startWebConsoleServer(options = {}, deps = {}) {
|
|
|
1978
2005
|
}
|
|
1979
2006
|
|
|
1980
2007
|
async function stopUntrackedStartupRuntime({ reason = "Stopped startup-managed LLM Router." } = {}) {
|
|
2008
|
+
if (!startupControlsEnabled) return false;
|
|
1981
2009
|
const startup = await startupStatusFn().catch(() => null);
|
|
1982
2010
|
if (!startup?.running) return false;
|
|
1983
2011
|
await stopStartupFn();
|
|
@@ -1987,6 +2015,12 @@ export async function startWebConsoleServer(options = {}, deps = {}) {
|
|
|
1987
2015
|
}
|
|
1988
2016
|
|
|
1989
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
|
+
|
|
1990
2024
|
await clearRuntimeStateFn();
|
|
1991
2025
|
const detail = await installStartupFn({
|
|
1992
2026
|
configPath,
|
|
@@ -2049,7 +2083,7 @@ export async function startWebConsoleServer(options = {}, deps = {}) {
|
|
|
2049
2083
|
};
|
|
2050
2084
|
}
|
|
2051
2085
|
|
|
2052
|
-
const startup = await startupStatusFn().catch(() => null);
|
|
2086
|
+
const startup = startupControlsEnabled ? await startupStatusFn().catch(() => null) : null;
|
|
2053
2087
|
const activeRuntime = await readManagedRuntime(configLocalServer);
|
|
2054
2088
|
if (activeRuntime) {
|
|
2055
2089
|
return {
|
|
@@ -2066,7 +2100,7 @@ export async function startWebConsoleServer(options = {}, deps = {}) {
|
|
|
2066
2100
|
await stopExternalRuntime(externalRuntime, {
|
|
2067
2101
|
reason: `Stopped an existing LLM Router instance so the web console can manage ${configLocalServer.host}:${configLocalServer.port} during ${reason}.`
|
|
2068
2102
|
});
|
|
2069
|
-
} else {
|
|
2103
|
+
} else if (startupControlsEnabled) {
|
|
2070
2104
|
await stopUntrackedStartupRuntime({
|
|
2071
2105
|
reason: `Stopped the startup-managed LLM Router instance so the web console can manage ${configLocalServer.host}:${configLocalServer.port} during ${reason}.`
|
|
2072
2106
|
});
|
|
@@ -2261,13 +2295,21 @@ export async function startWebConsoleServer(options = {}, deps = {}) {
|
|
|
2261
2295
|
const configState = await readConfigState(configPath);
|
|
2262
2296
|
const configLocalServer = getConfigLocalServer(configState);
|
|
2263
2297
|
const activityLog = resolveActivityLogSnapshot(configState.normalizedConfig);
|
|
2264
|
-
const startup =
|
|
2265
|
-
|
|
2266
|
-
|
|
2267
|
-
|
|
2268
|
-
|
|
2269
|
-
|
|
2270
|
-
|
|
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
|
+
};
|
|
2271
2313
|
const managedRuntime = await readManagedRuntime(configLocalServer);
|
|
2272
2314
|
const externalRuntime = managedRuntime ? null : await readExternalRuntime(configLocalServer);
|
|
2273
2315
|
const portProbe = probeRouterPort(configLocalServer);
|
|
@@ -2306,9 +2348,12 @@ export async function startWebConsoleServer(options = {}, deps = {}) {
|
|
|
2306
2348
|
router: routerSnapshot,
|
|
2307
2349
|
startup: {
|
|
2308
2350
|
...startup,
|
|
2309
|
-
label: formatStartupLabel(startup),
|
|
2310
|
-
friendlyDetail:
|
|
2311
|
-
|
|
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
|
|
2312
2357
|
},
|
|
2313
2358
|
ampClient: {
|
|
2314
2359
|
global: ampClientGlobal
|
|
@@ -2464,8 +2509,8 @@ export async function startWebConsoleServer(options = {}, deps = {}) {
|
|
|
2464
2509
|
nextOptions = persisted.savedSettings;
|
|
2465
2510
|
}
|
|
2466
2511
|
|
|
2467
|
-
const startup = await startupStatusFn().catch(() => null);
|
|
2468
|
-
const preferStartupOwnership = Boolean(startup?.installed);
|
|
2512
|
+
const startup = startupControlsEnabled ? await startupStatusFn().catch(() => null) : null;
|
|
2513
|
+
const preferStartupOwnership = startupControlsEnabled && Boolean(startup?.installed);
|
|
2469
2514
|
const runningRuntime = await readManagedRuntime(nextOptions);
|
|
2470
2515
|
const webConsoleConflict = getWebConsoleConflictMessage(nextOptions);
|
|
2471
2516
|
|
|
@@ -2492,7 +2537,7 @@ export async function startWebConsoleServer(options = {}, deps = {}) {
|
|
|
2492
2537
|
await stopExternalRuntime(externalRuntime, {
|
|
2493
2538
|
reason: "Stopped another LLM Router instance before starting the managed router."
|
|
2494
2539
|
});
|
|
2495
|
-
} else {
|
|
2540
|
+
} else if (startupControlsEnabled) {
|
|
2496
2541
|
await stopUntrackedStartupRuntime({
|
|
2497
2542
|
reason: "Stopped the startup-managed LLM Router instance before starting the managed router."
|
|
2498
2543
|
});
|
|
@@ -3487,6 +3532,7 @@ export async function startWebConsoleServer(options = {}, deps = {}) {
|
|
|
3487
3532
|
endpointUrl,
|
|
3488
3533
|
apiKey,
|
|
3489
3534
|
bindings,
|
|
3535
|
+
config: nextConfig,
|
|
3490
3536
|
captureBackup: true
|
|
3491
3537
|
});
|
|
3492
3538
|
addLog("success", "Factory Droid routing enabled.", patchResult.baseUrl);
|
|
@@ -3537,6 +3583,7 @@ export async function startWebConsoleServer(options = {}, deps = {}) {
|
|
|
3537
3583
|
endpointUrl,
|
|
3538
3584
|
apiKey,
|
|
3539
3585
|
bindings,
|
|
3586
|
+
config: configState.normalizedConfig,
|
|
3540
3587
|
captureBackup: false
|
|
3541
3588
|
});
|
|
3542
3589
|
addLog("success", "Factory Droid model bindings updated.", patchResult.bindings.defaultModel || "Default");
|
|
@@ -3722,6 +3769,10 @@ export async function startWebConsoleServer(options = {}, deps = {}) {
|
|
|
3722
3769
|
}
|
|
3723
3770
|
|
|
3724
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
|
+
}
|
|
3725
3776
|
const body = await readJsonBody(req);
|
|
3726
3777
|
const configState = await readConfigState(configPath);
|
|
3727
3778
|
if (configState.parseError) {
|
|
@@ -3784,6 +3835,10 @@ export async function startWebConsoleServer(options = {}, deps = {}) {
|
|
|
3784
3835
|
}
|
|
3785
3836
|
|
|
3786
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
|
+
}
|
|
3787
3842
|
await readJsonBody(req);
|
|
3788
3843
|
const statusBefore = await startupStatusFn().catch(() => null);
|
|
3789
3844
|
if (!statusBefore?.installed) {
|
|
@@ -4234,6 +4289,7 @@ export async function startWebConsoleServer(options = {}, deps = {}) {
|
|
|
4234
4289
|
|
|
4235
4290
|
addLog("info", `Web console listening on http://${formatHostForUrl(host, actualWebPort)}`);
|
|
4236
4291
|
if (devMode) addLog("info", "Development mode enabled for web assets.");
|
|
4292
|
+
if (!startupControlsEnabled) addLog("info", "Startup service controls disabled in dev mode.");
|
|
4237
4293
|
startConfigWatcher();
|
|
4238
4294
|
startActivityLogWatcher();
|
|
4239
4295
|
|
|
@@ -64,6 +64,160 @@ export const FACTORY_DROID_REASONING_EFFORT_VALUES = Object.freeze([
|
|
|
64
64
|
"high"
|
|
65
65
|
]);
|
|
66
66
|
|
|
67
|
+
function stripFactoryDroidRouterModelIdPrefix(value) {
|
|
68
|
+
const normalized = String(value || "").trim();
|
|
69
|
+
if (normalized.startsWith("custom:")) return normalized.slice("custom:".length).trim();
|
|
70
|
+
return normalized;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function sanitizeFactoryDroidRouterModelIdPart(value) {
|
|
74
|
+
return String(value || "")
|
|
75
|
+
.trim()
|
|
76
|
+
.replace(/[/:]+/g, "-")
|
|
77
|
+
.replace(/\s+/g, "-")
|
|
78
|
+
.replace(/[^A-Za-z0-9._-]+/g, "-")
|
|
79
|
+
.replace(/-+/g, "-")
|
|
80
|
+
.replace(/^-+|-+$/g, "");
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function formatFactoryDroidDisplayNameBase(value) {
|
|
84
|
+
const normalized = String(value || "").trim();
|
|
85
|
+
if (!normalized) return "";
|
|
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(" ");
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export function isFactoryDroidRouterModelId(value) {
|
|
114
|
+
const normalized = stripFactoryDroidRouterModelIdPrefix(value);
|
|
115
|
+
return normalized.startsWith("llm-");
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export function parseFactoryDroidRouterModelId(value) {
|
|
119
|
+
const normalized = stripFactoryDroidRouterModelIdPrefix(value);
|
|
120
|
+
if (!normalized.startsWith("llm-")) return null;
|
|
121
|
+
|
|
122
|
+
if (normalized.startsWith("llm-alias:")) {
|
|
123
|
+
const aliasId = normalized.slice("llm-alias:".length).trim();
|
|
124
|
+
return aliasId
|
|
125
|
+
? {
|
|
126
|
+
kind: "alias",
|
|
127
|
+
aliasId,
|
|
128
|
+
routeRef: aliasId
|
|
129
|
+
}
|
|
130
|
+
: null;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
if (normalized.startsWith("llm-alias-")) {
|
|
134
|
+
const aliasId = normalized.slice("llm-alias-".length).trim();
|
|
135
|
+
return aliasId
|
|
136
|
+
? {
|
|
137
|
+
kind: "alias",
|
|
138
|
+
aliasId,
|
|
139
|
+
routeRef: ""
|
|
140
|
+
}
|
|
141
|
+
: null;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const body = normalized.slice("llm-".length);
|
|
145
|
+
const separatorIndex = body.indexOf(":");
|
|
146
|
+
if (separatorIndex <= 0) return null;
|
|
147
|
+
|
|
148
|
+
const providerId = body.slice(0, separatorIndex).trim();
|
|
149
|
+
const modelId = body.slice(separatorIndex + 1).trim();
|
|
150
|
+
if (!providerId || !modelId) return null;
|
|
151
|
+
|
|
152
|
+
return {
|
|
153
|
+
kind: "model",
|
|
154
|
+
providerId,
|
|
155
|
+
modelId,
|
|
156
|
+
routeRef: `${providerId}/${modelId}`
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
export function resolveFactoryDroidRouterModelRef(value) {
|
|
161
|
+
const normalized = String(value || "").trim();
|
|
162
|
+
if (!normalized) return "";
|
|
163
|
+
return parseFactoryDroidRouterModelId(normalized)?.routeRef || normalized;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
export function buildFactoryDroidRouterModelId(modelRef, { kind = "" } = {}) {
|
|
167
|
+
const normalizedModelRef = String(modelRef || "").trim();
|
|
168
|
+
if (!normalizedModelRef) return "";
|
|
169
|
+
if (normalizedModelRef.startsWith("custom:llm-")) {
|
|
170
|
+
const parsed = parseFactoryDroidRouterModelId(normalizedModelRef);
|
|
171
|
+
return parsed?.routeRef
|
|
172
|
+
? buildFactoryDroidRouterModelId(parsed.routeRef, { kind: parsed.kind })
|
|
173
|
+
: normalizedModelRef;
|
|
174
|
+
}
|
|
175
|
+
if (normalizedModelRef.startsWith("llm-")) {
|
|
176
|
+
const parsed = parseFactoryDroidRouterModelId(normalizedModelRef);
|
|
177
|
+
return parsed?.routeRef
|
|
178
|
+
? buildFactoryDroidRouterModelId(parsed.routeRef, { kind: parsed.kind })
|
|
179
|
+
: `custom:${normalizedModelRef}`;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
const explicitKind = String(kind || "").trim().toLowerCase();
|
|
183
|
+
if (explicitKind === "alias") {
|
|
184
|
+
const aliasId = sanitizeFactoryDroidRouterModelIdPart(normalizedModelRef);
|
|
185
|
+
return aliasId ? `custom:llm-alias-${aliasId}` : "";
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
if (explicitKind === "model") {
|
|
189
|
+
const separatorIndex = normalizedModelRef.indexOf("/");
|
|
190
|
+
if (separatorIndex <= 0 || separatorIndex >= normalizedModelRef.length - 1) return "";
|
|
191
|
+
const providerId = normalizedModelRef.slice(0, separatorIndex).trim();
|
|
192
|
+
const modelId = normalizedModelRef.slice(separatorIndex + 1).trim();
|
|
193
|
+
const providerSlug = sanitizeFactoryDroidRouterModelIdPart(providerId);
|
|
194
|
+
const modelSlug = sanitizeFactoryDroidRouterModelIdPart(modelId);
|
|
195
|
+
return providerSlug && modelSlug ? `custom:llm-${providerSlug}-${modelSlug}` : "";
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
if (!normalizedModelRef.includes("/")) {
|
|
199
|
+
return buildFactoryDroidRouterModelId(normalizedModelRef, { kind: "alias" });
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
return buildFactoryDroidRouterModelId(normalizedModelRef, { kind: "model" });
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
export function buildFactoryDroidRouterDisplayName(modelRef, { kind = "", providerName = "" } = {}) {
|
|
206
|
+
const normalizedModelRef = String(modelRef || "").trim();
|
|
207
|
+
if (!normalizedModelRef) return "";
|
|
208
|
+
|
|
209
|
+
const explicitKind = String(kind || "").trim().toLowerCase();
|
|
210
|
+
const inferredKind = explicitKind || (normalizedModelRef.includes("/") ? "model" : "alias");
|
|
211
|
+
if (inferredKind === "alias") {
|
|
212
|
+
return `${formatFactoryDroidDisplayNameBase(normalizedModelRef)} - LLM Router (Alias)`;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
const modelName = normalizedModelRef.includes("/")
|
|
216
|
+
? normalizedModelRef.slice(normalizedModelRef.indexOf("/") + 1).trim()
|
|
217
|
+
: normalizedModelRef;
|
|
218
|
+
return `${formatFactoryDroidDisplayNameBase(modelName)} - LLM Router (${formatFactoryDroidProviderLabel(providerName)})`;
|
|
219
|
+
}
|
|
220
|
+
|
|
67
221
|
export function normalizeFactoryDroidReasoningEffort(value) {
|
|
68
222
|
const normalized = String(value || "").trim().toLowerCase();
|
|
69
223
|
return FACTORY_DROID_REASONING_EFFORT_VALUES.includes(normalized) ? normalized : "";
|
|
@@ -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
|
|