@khanglvm/llm-router 2.3.4 → 2.3.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/CHANGELOG.md +10 -0
- package/README.md +1 -0
- package/package.json +1 -1
- package/src/cli/router-module.js +5 -2
- package/src/node/coding-tool-config.js +270 -31
- package/src/node/web-console-client.js +20 -20
- package/src/node/web-console-server.js +12 -1
- package/src/runtime/handler/reasoning-effort.js +148 -34
- package/src/shared/coding-tool-bindings.js +133 -0
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [2.3.6] - 2026-04-18
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
- Factory Droid routing now injects every managed alias/provider model as its own router-managed `customModels` entry, writes friendly custom model `displayName` labels for the Droid CLI picker, and stores selected defaults as explicit `custom:llm-*` IDs in `model`, `sessionDefaultSettings.model`, `missionOrchestratorModel`, and `missionModelSettings.*` so Droid resolves the router-managed custom provider instead of falling back to native built-in models.
|
|
14
|
+
|
|
15
|
+
## [2.3.5] - 2026-04-17
|
|
16
|
+
|
|
17
|
+
### Fixed
|
|
18
|
+
- Added model-aware reasoning/effort conversion so routed requests automatically fall back to the safest supported effort level for the actual backend model, including GPT-5 Codex/OpenAI targets and Claude Opus 4.6 vs 4.7 targets behind the same alias.
|
|
19
|
+
|
|
10
20
|
## [2.3.4] - 2026-04-17
|
|
11
21
|
|
|
12
22
|
### Fixed
|
package/README.md
CHANGED
|
@@ -62,6 +62,7 @@ Route Claude Code through the gateway with per-tier model bindings.
|
|
|
62
62
|
### Factory Droid
|
|
63
63
|
|
|
64
64
|
Route Factory Droid through the gateway via a managed custom model entry with reasoning effort control.
|
|
65
|
+
LLM Router injects router-managed `customModels` entries for aliases and provider/model routes, then writes Factory defaults as `custom:llm-*` IDs so Droid selects the custom provider entry instead of a native built-in model with the same name.
|
|
65
66
|
|
|
66
67
|
### Web Search
|
|
67
68
|
|
package/package.json
CHANGED
package/src/cli/router-module.js
CHANGED
|
@@ -3853,7 +3853,8 @@ async function buildCodingToolRoutingSnapshot({
|
|
|
3853
3853
|
}));
|
|
3854
3854
|
const factoryDroid = await readFactoryDroidRoutingState({
|
|
3855
3855
|
settingsFilePath: readArg(args, ["factory-droid-settings-file", "factoryDroidSettingsFile"], ""),
|
|
3856
|
-
endpointUrl
|
|
3856
|
+
endpointUrl,
|
|
3857
|
+
config
|
|
3857
3858
|
}).catch((error) => ({
|
|
3858
3859
|
tool: "factory-droid",
|
|
3859
3860
|
settingsFilePath: resolveFactoryDroidSettingsFilePath({}),
|
|
@@ -6635,7 +6636,8 @@ async function doSetFactoryDroidRouting(context) {
|
|
|
6635
6636
|
|
|
6636
6637
|
const existingState = await readFactoryDroidRoutingState({
|
|
6637
6638
|
settingsFilePath,
|
|
6638
|
-
endpointUrl
|
|
6639
|
+
endpointUrl,
|
|
6640
|
+
config
|
|
6639
6641
|
});
|
|
6640
6642
|
const apiKey = String(
|
|
6641
6643
|
readArg(args, ["master-key", "masterKey", "api-key", "apiKey"], config?.masterKey || "") || ""
|
|
@@ -6679,6 +6681,7 @@ async function doSetFactoryDroidRouting(context) {
|
|
|
6679
6681
|
endpointUrl,
|
|
6680
6682
|
apiKey,
|
|
6681
6683
|
bindings,
|
|
6684
|
+
config,
|
|
6682
6685
|
captureBackup: true
|
|
6683
6686
|
});
|
|
6684
6687
|
return {
|
|
@@ -4,14 +4,18 @@ import { promises as fs } from "node:fs";
|
|
|
4
4
|
import {
|
|
5
5
|
CODEX_CLI_INHERIT_MODEL_VALUE,
|
|
6
6
|
CLAUDE_CODE_EFFORT_LEVEL_SETTINGS_JSON_VALUE,
|
|
7
|
+
buildFactoryDroidRouterDisplayName,
|
|
8
|
+
buildFactoryDroidRouterModelId,
|
|
7
9
|
isCodexCliInheritModelBinding,
|
|
10
|
+
isFactoryDroidRouterModelId,
|
|
8
11
|
mapClaudeCodeThinkingLevelToTokens,
|
|
9
12
|
mapClaudeCodeThinkingTokensToLevel,
|
|
10
13
|
normalizeClaudeCodeThinkingLevel,
|
|
11
14
|
normalizeClaudeCodeEffortLevel,
|
|
12
15
|
migrateLegacyThinkingTokensToEffortLevel,
|
|
13
16
|
normalizeCodexCliReasoningEffort,
|
|
14
|
-
normalizeFactoryDroidReasoningEffort
|
|
17
|
+
normalizeFactoryDroidReasoningEffort,
|
|
18
|
+
resolveFactoryDroidRouterModelRef
|
|
15
19
|
} from "../shared/coding-tool-bindings.js";
|
|
16
20
|
|
|
17
21
|
const BACKUP_SUFFIX = ".llm_router_backup";
|
|
@@ -985,10 +989,158 @@ function buildFactoryDroidBaseUrl(endpointUrl) {
|
|
|
985
989
|
return normalized ? `${normalized}/openai/v1` : "";
|
|
986
990
|
}
|
|
987
991
|
|
|
992
|
+
function collectFactoryDroidAvailableModels(config = {}, bindings = {}) {
|
|
993
|
+
const refs = [];
|
|
994
|
+
const seen = new Set();
|
|
995
|
+
const push = (value) => {
|
|
996
|
+
const normalized = String(value || "").trim();
|
|
997
|
+
if (!normalized || seen.has(normalized)) return;
|
|
998
|
+
seen.add(normalized);
|
|
999
|
+
refs.push(normalized);
|
|
1000
|
+
};
|
|
1001
|
+
|
|
1002
|
+
const aliases = config?.modelAliases && typeof config.modelAliases === "object" && !Array.isArray(config.modelAliases)
|
|
1003
|
+
? config.modelAliases
|
|
1004
|
+
: {};
|
|
1005
|
+
for (const aliasId of Object.keys(aliases)) {
|
|
1006
|
+
push(aliasId);
|
|
1007
|
+
}
|
|
1008
|
+
|
|
1009
|
+
push(bindings?.defaultModel);
|
|
1010
|
+
push(bindings?.missionOrchestratorModel);
|
|
1011
|
+
push(bindings?.missionWorkerModel);
|
|
1012
|
+
push(bindings?.missionValidatorModel);
|
|
1013
|
+
|
|
1014
|
+
for (const provider of Array.isArray(config?.providers) ? config.providers : []) {
|
|
1015
|
+
if (provider?.enabled === false) continue;
|
|
1016
|
+
const providerId = String(provider?.id || "").trim();
|
|
1017
|
+
if (!providerId) continue;
|
|
1018
|
+
for (const model of Array.isArray(provider?.models) ? provider.models : []) {
|
|
1019
|
+
if (model?.enabled === false) continue;
|
|
1020
|
+
const modelId = String(model?.id || "").trim();
|
|
1021
|
+
if (!modelId) continue;
|
|
1022
|
+
push(`${providerId}/${modelId}`);
|
|
1023
|
+
}
|
|
1024
|
+
}
|
|
1025
|
+
|
|
1026
|
+
return refs;
|
|
1027
|
+
}
|
|
1028
|
+
|
|
1029
|
+
function buildFactoryDroidAvailableModelDescriptors(config = {}, bindings = {}) {
|
|
1030
|
+
return collectFactoryDroidAvailableModels(config, bindings)
|
|
1031
|
+
.map((modelRef) => {
|
|
1032
|
+
const kind = modelRef.includes("/") ? "model" : "alias";
|
|
1033
|
+
return {
|
|
1034
|
+
modelRef,
|
|
1035
|
+
kind,
|
|
1036
|
+
id: buildFactoryDroidRouterModelId(modelRef, { kind }),
|
|
1037
|
+
displayName: buildFactoryDroidRouterDisplayName(modelRef, { kind })
|
|
1038
|
+
};
|
|
1039
|
+
})
|
|
1040
|
+
.filter((entry) => String(entry.id || "").trim() && String(entry.modelRef || "").trim());
|
|
1041
|
+
}
|
|
1042
|
+
|
|
1043
|
+
function buildFactoryDroidRouteLookup(config = {}, bindings = {}) {
|
|
1044
|
+
const descriptors = buildFactoryDroidAvailableModelDescriptors(config, bindings);
|
|
1045
|
+
const byId = new Map();
|
|
1046
|
+
const byDisplayName = new Map();
|
|
1047
|
+
|
|
1048
|
+
for (const descriptor of descriptors) {
|
|
1049
|
+
if (descriptor.id) byId.set(descriptor.id, descriptor.modelRef);
|
|
1050
|
+
if (descriptor.displayName) byDisplayName.set(descriptor.displayName, descriptor.modelRef);
|
|
1051
|
+
}
|
|
1052
|
+
|
|
1053
|
+
return {
|
|
1054
|
+
descriptors,
|
|
1055
|
+
byId,
|
|
1056
|
+
byDisplayName
|
|
1057
|
+
};
|
|
1058
|
+
}
|
|
1059
|
+
|
|
1060
|
+
function resolveFactoryDroidRouteRefFromLookup(value, routeLookup) {
|
|
1061
|
+
const normalizedValue = String(value || "").trim();
|
|
1062
|
+
if (!normalizedValue || !routeLookup || typeof routeLookup !== "object") return "";
|
|
1063
|
+
if (routeLookup.byId instanceof Map && routeLookup.byId.has(normalizedValue)) {
|
|
1064
|
+
return String(routeLookup.byId.get(normalizedValue) || "").trim();
|
|
1065
|
+
}
|
|
1066
|
+
if (routeLookup.byDisplayName instanceof Map && routeLookup.byDisplayName.has(normalizedValue)) {
|
|
1067
|
+
return String(routeLookup.byDisplayName.get(normalizedValue) || "").trim();
|
|
1068
|
+
}
|
|
1069
|
+
return "";
|
|
1070
|
+
}
|
|
1071
|
+
|
|
1072
|
+
function getFactoryDroidCustomModelEntryByValue(customModels, value, { preferRouterManaged = false } = {}) {
|
|
1073
|
+
const normalizedValue = String(value || "").trim();
|
|
1074
|
+
if (!normalizedValue || !Array.isArray(customModels)) return null;
|
|
1075
|
+
|
|
1076
|
+
const entries = preferRouterManaged
|
|
1077
|
+
? [
|
|
1078
|
+
...customModels.filter((entry) => isFactoryDroidRouterManagedEntry(entry)),
|
|
1079
|
+
...customModels.filter((entry) => !isFactoryDroidRouterManagedEntry(entry))
|
|
1080
|
+
]
|
|
1081
|
+
: customModels;
|
|
1082
|
+
|
|
1083
|
+
return entries.find((entry) => entry && typeof entry === "object" && (
|
|
1084
|
+
String(entry.id || "").trim() === normalizedValue
|
|
1085
|
+
|| String(entry.model || "").trim() === normalizedValue
|
|
1086
|
+
|| String(entry.displayName || "").trim() === normalizedValue
|
|
1087
|
+
)) || null;
|
|
1088
|
+
}
|
|
1089
|
+
|
|
1090
|
+
function resolveFactoryDroidBindingModelRef(value, customModels, routeLookup = null) {
|
|
1091
|
+
const normalizedValue = String(value || "").trim();
|
|
1092
|
+
if (!normalizedValue) return "";
|
|
1093
|
+
const matchedEntry = getFactoryDroidCustomModelEntryByValue(customModels, normalizedValue, { preferRouterManaged: true });
|
|
1094
|
+
if (!matchedEntry) {
|
|
1095
|
+
return resolveFactoryDroidRouteRefFromLookup(normalizedValue, routeLookup)
|
|
1096
|
+
|| resolveFactoryDroidRouterModelRef(normalizedValue);
|
|
1097
|
+
}
|
|
1098
|
+
return resolveFactoryDroidRouterModelRef(
|
|
1099
|
+
String(matchedEntry.model || matchedEntry.displayName || matchedEntry.id || normalizedValue).trim()
|
|
1100
|
+
);
|
|
1101
|
+
}
|
|
1102
|
+
|
|
1103
|
+
function getNextFactoryDroidCustomModelIndex(customModels) {
|
|
1104
|
+
if (!Array.isArray(customModels) || customModels.length === 0) return 0;
|
|
1105
|
+
let maxIndex = -1;
|
|
1106
|
+
for (const entry of customModels) {
|
|
1107
|
+
const parsed = Number(entry?.index);
|
|
1108
|
+
if (Number.isFinite(parsed)) maxIndex = Math.max(maxIndex, parsed);
|
|
1109
|
+
}
|
|
1110
|
+
return maxIndex >= 0 ? (maxIndex + 1) : customModels.length;
|
|
1111
|
+
}
|
|
1112
|
+
|
|
1113
|
+
function buildFactoryDroidCustomModelId(modelRef, index) {
|
|
1114
|
+
return buildFactoryDroidRouterModelId(modelRef) || `custom:llm-alias-llm-router-${Number(index)}`;
|
|
1115
|
+
}
|
|
1116
|
+
|
|
1117
|
+
function resolveFactoryDroidBindingSelectionValue(value, customModels, routeLookup = null) {
|
|
1118
|
+
const normalizedValue = String(value || "").trim();
|
|
1119
|
+
if (!normalizedValue) return "";
|
|
1120
|
+
const matchedEntry = getFactoryDroidCustomModelEntryByValue(customModels, normalizedValue, { preferRouterManaged: true });
|
|
1121
|
+
if (matchedEntry) {
|
|
1122
|
+
const matchedId = String(matchedEntry.id || "").trim();
|
|
1123
|
+
const preferredId = buildFactoryDroidCustomModelId(
|
|
1124
|
+
resolveFactoryDroidBindingModelRef(normalizedValue, customModels, routeLookup),
|
|
1125
|
+
Number(matchedEntry.index) || 0
|
|
1126
|
+
);
|
|
1127
|
+
if (matchedEntry[FACTORY_DROID_ROUTER_MARKER] === true || isFactoryDroidRouterModelId(matchedId)) {
|
|
1128
|
+
return preferredId || matchedId || normalizedValue;
|
|
1129
|
+
}
|
|
1130
|
+
return matchedId || preferredId || normalizedValue;
|
|
1131
|
+
}
|
|
1132
|
+
const resolvedRouteRef = resolveFactoryDroidRouteRefFromLookup(normalizedValue, routeLookup);
|
|
1133
|
+
if (resolvedRouteRef) return buildFactoryDroidCustomModelId(resolvedRouteRef, 0) || normalizedValue;
|
|
1134
|
+
return buildFactoryDroidCustomModelId(
|
|
1135
|
+
resolveFactoryDroidBindingModelRef(normalizedValue, customModels, routeLookup),
|
|
1136
|
+
0
|
|
1137
|
+
) || normalizedValue;
|
|
1138
|
+
}
|
|
1139
|
+
|
|
988
1140
|
function findRouterManagedCustomModelIndex(customModels) {
|
|
989
1141
|
if (!Array.isArray(customModels)) return -1;
|
|
990
1142
|
return customModels.findIndex(
|
|
991
|
-
(entry) => entry
|
|
1143
|
+
(entry) => isFactoryDroidRouterManagedEntry(entry)
|
|
992
1144
|
);
|
|
993
1145
|
}
|
|
994
1146
|
|
|
@@ -997,10 +1149,27 @@ function getRouterManagedCustomModel(customModels) {
|
|
|
997
1149
|
return routerIndex >= 0 ? customModels[routerIndex] : null;
|
|
998
1150
|
}
|
|
999
1151
|
|
|
1000
|
-
function
|
|
1152
|
+
function isFactoryDroidRouterManagedEntry(entry, { baseUrl = "" } = {}) {
|
|
1153
|
+
if (!entry || typeof entry !== "object" || Array.isArray(entry)) return false;
|
|
1154
|
+
if (entry[FACTORY_DROID_ROUTER_MARKER] === true) return true;
|
|
1155
|
+
|
|
1156
|
+
const entryId = String(entry.id || "").trim();
|
|
1157
|
+
if (isFactoryDroidRouterModelId(entryId)) return true;
|
|
1158
|
+
|
|
1159
|
+
const provider = String(entry.provider || "").trim();
|
|
1160
|
+
if (provider !== FACTORY_DROID_ROUTER_PROVIDER) return false;
|
|
1161
|
+
|
|
1162
|
+
const entryBaseUrl = String(entry.baseUrl || "").trim();
|
|
1163
|
+
if (baseUrl && entryBaseUrl === String(baseUrl || "").trim()) return true;
|
|
1164
|
+
|
|
1165
|
+
const apiKey = String(entry.apiKey || "").trim();
|
|
1166
|
+
return apiKey.startsWith("gw_") && entryBaseUrl.includes("/openai/v1");
|
|
1167
|
+
}
|
|
1168
|
+
|
|
1169
|
+
function stripRouterManagedCustomModels(customModels, { baseUrl = "" } = {}) {
|
|
1001
1170
|
if (!Array.isArray(customModels)) return [];
|
|
1002
1171
|
return customModels.filter(
|
|
1003
|
-
(entry) => !(entry
|
|
1172
|
+
(entry) => !isFactoryDroidRouterManagedEntry(entry, { baseUrl })
|
|
1004
1173
|
);
|
|
1005
1174
|
}
|
|
1006
1175
|
|
|
@@ -1136,11 +1305,13 @@ export async function readFactoryDroidRoutingState({
|
|
|
1136
1305
|
settingsFilePath = "",
|
|
1137
1306
|
backupFilePath = "",
|
|
1138
1307
|
endpointUrl = "",
|
|
1308
|
+
config = {},
|
|
1139
1309
|
homeDir = os.homedir()
|
|
1140
1310
|
} = {}) {
|
|
1141
1311
|
const resolvedSettingsPath = path.resolve(String(settingsFilePath || resolveFactoryDroidSettingsFilePath({ homeDir })).trim());
|
|
1142
1312
|
const resolvedBackupPath = path.resolve(String(backupFilePath || resolveCodingToolBackupFilePath(resolvedSettingsPath)).trim());
|
|
1143
1313
|
const expectedBaseUrl = buildFactoryDroidBaseUrl(endpointUrl);
|
|
1314
|
+
const routeLookup = buildFactoryDroidRouteLookup(config);
|
|
1144
1315
|
const settingsState = await readJsonObjectFile(resolvedSettingsPath, `Factory Droid settings file '${resolvedSettingsPath}'`);
|
|
1145
1316
|
const backupState = await readJsonObjectFile(resolvedBackupPath, `Backup file '${resolvedBackupPath}'`);
|
|
1146
1317
|
const customModels = Array.isArray(settingsState.data?.customModels) ? settingsState.data.customModels : [];
|
|
@@ -1153,6 +1324,15 @@ export async function readFactoryDroidRoutingState({
|
|
|
1153
1324
|
&& configuredBaseUrl === expectedBaseUrl
|
|
1154
1325
|
);
|
|
1155
1326
|
|
|
1327
|
+
const resolvedDefaultModelValue = getNestedObjectValue(settingsState.data, ["sessionDefaultSettings", "model"])
|
|
1328
|
+
|| settingsState.data?.model
|
|
1329
|
+
|| routerEntry?.id
|
|
1330
|
+
|| routerEntry?.model
|
|
1331
|
+
|| "";
|
|
1332
|
+
const resolvedMissionOrchestratorValue = settingsState.data?.missionOrchestratorModel || "";
|
|
1333
|
+
const resolvedMissionWorkerValue = getNestedObjectValue(settingsState.data, ["missionModelSettings", "workerModel"]) || "";
|
|
1334
|
+
const resolvedMissionValidatorValue = getNestedObjectValue(settingsState.data, ["missionModelSettings", "validationWorkerModel"]) || "";
|
|
1335
|
+
|
|
1156
1336
|
return {
|
|
1157
1337
|
tool: "factory-droid",
|
|
1158
1338
|
settingsFilePath: resolvedSettingsPath,
|
|
@@ -1163,13 +1343,17 @@ export async function readFactoryDroidRoutingState({
|
|
|
1163
1343
|
configuredBaseUrl,
|
|
1164
1344
|
configuredProvider,
|
|
1165
1345
|
bindings: normalizeFactoryDroidBindings({
|
|
1166
|
-
defaultModel:
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1346
|
+
defaultModel: resolveFactoryDroidBindingModelRef(resolvedDefaultModelValue, customModels, routeLookup),
|
|
1347
|
+
missionOrchestratorModel: resolveFactoryDroidBindingModelRef(resolvedMissionOrchestratorValue, customModels, routeLookup),
|
|
1348
|
+
missionWorkerModel: resolveFactoryDroidBindingModelRef(resolvedMissionWorkerValue, customModels, routeLookup),
|
|
1349
|
+
missionValidatorModel: resolveFactoryDroidBindingModelRef(resolvedMissionValidatorValue, customModels, routeLookup),
|
|
1350
|
+
reasoningEffort: normalizeFactoryDroidReasoningEffort(settingsState.data?.reasoningEffort)
|
|
1351
|
+
}),
|
|
1352
|
+
bindingIds: normalizeFactoryDroidBindings({
|
|
1353
|
+
defaultModel: resolveFactoryDroidBindingSelectionValue(resolvedDefaultModelValue, customModels, routeLookup),
|
|
1354
|
+
missionOrchestratorModel: resolveFactoryDroidBindingSelectionValue(resolvedMissionOrchestratorValue, customModels, routeLookup),
|
|
1355
|
+
missionWorkerModel: resolveFactoryDroidBindingSelectionValue(resolvedMissionWorkerValue, customModels, routeLookup),
|
|
1356
|
+
missionValidatorModel: resolveFactoryDroidBindingSelectionValue(resolvedMissionValidatorValue, customModels, routeLookup),
|
|
1173
1357
|
reasoningEffort: normalizeFactoryDroidReasoningEffort(settingsState.data?.reasoningEffort)
|
|
1174
1358
|
})
|
|
1175
1359
|
};
|
|
@@ -1181,6 +1365,7 @@ export async function patchFactoryDroidSettingsFile({
|
|
|
1181
1365
|
endpointUrl = "",
|
|
1182
1366
|
apiKey = "",
|
|
1183
1367
|
bindings = {},
|
|
1368
|
+
config = {},
|
|
1184
1369
|
captureBackup = true,
|
|
1185
1370
|
homeDir = os.homedir()
|
|
1186
1371
|
} = {}) {
|
|
@@ -1189,6 +1374,7 @@ export async function patchFactoryDroidSettingsFile({
|
|
|
1189
1374
|
const baseUrl = buildFactoryDroidBaseUrl(endpointUrl);
|
|
1190
1375
|
const normalizedApiKey = String(apiKey || "").trim();
|
|
1191
1376
|
const normalizedBindings = normalizeFactoryDroidBindings(bindings);
|
|
1377
|
+
const routeLookup = buildFactoryDroidRouteLookup(config);
|
|
1192
1378
|
|
|
1193
1379
|
if (!baseUrl) {
|
|
1194
1380
|
throw new Error("Factory Droid endpoint URL must be a valid http:// or https:// URL.");
|
|
@@ -1203,51 +1389,89 @@ export async function patchFactoryDroidSettingsFile({
|
|
|
1203
1389
|
const nextSettings = settingsState.data && typeof settingsState.data === "object" && !Array.isArray(settingsState.data)
|
|
1204
1390
|
? structuredClone(settingsState.data)
|
|
1205
1391
|
: {};
|
|
1392
|
+
const existingCustomModels = Array.isArray(nextSettings.customModels) ? nextSettings.customModels : [];
|
|
1206
1393
|
|
|
1207
1394
|
if (captureBackup && !backupHasData(existingBackup)) {
|
|
1208
1395
|
const backup = settingsState.existed ? captureFactoryDroidBackup(nextSettings) : {};
|
|
1209
1396
|
await writeJsonObjectFile(resolvedBackupPath, backup);
|
|
1210
1397
|
}
|
|
1211
1398
|
|
|
1212
|
-
const
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|| "llm-router",
|
|
1220
|
-
displayName: "LLM Router",
|
|
1221
|
-
baseUrl,
|
|
1222
|
-
apiKey: normalizedApiKey,
|
|
1223
|
-
provider: FACTORY_DROID_ROUTER_PROVIDER
|
|
1224
|
-
};
|
|
1399
|
+
const resolvedBindings = normalizeFactoryDroidBindings({
|
|
1400
|
+
defaultModel: resolveFactoryDroidBindingModelRef(normalizedBindings.defaultModel, existingCustomModels, routeLookup),
|
|
1401
|
+
missionOrchestratorModel: resolveFactoryDroidBindingModelRef(normalizedBindings.missionOrchestratorModel, existingCustomModels, routeLookup),
|
|
1402
|
+
missionWorkerModel: resolveFactoryDroidBindingModelRef(normalizedBindings.missionWorkerModel, existingCustomModels, routeLookup),
|
|
1403
|
+
missionValidatorModel: resolveFactoryDroidBindingModelRef(normalizedBindings.missionValidatorModel, existingCustomModels, routeLookup),
|
|
1404
|
+
reasoningEffort: normalizedBindings.reasoningEffort
|
|
1405
|
+
});
|
|
1225
1406
|
|
|
1226
|
-
customModels
|
|
1407
|
+
const customModels = stripRouterManagedCustomModels(existingCustomModels, { baseUrl });
|
|
1408
|
+
const availableModels = buildFactoryDroidAvailableModelDescriptors(config, resolvedBindings);
|
|
1409
|
+
const routerEntryStartIndex = getNextFactoryDroidCustomModelIndex(customModels);
|
|
1410
|
+
const routerEntries = availableModels.length > 0
|
|
1411
|
+
? availableModels.map((descriptor, index) => {
|
|
1412
|
+
const entryIndex = routerEntryStartIndex + index;
|
|
1413
|
+
const modelId = buildFactoryDroidCustomModelId(descriptor.modelRef, entryIndex);
|
|
1414
|
+
return {
|
|
1415
|
+
[FACTORY_DROID_ROUTER_MARKER]: true,
|
|
1416
|
+
model: descriptor.modelRef,
|
|
1417
|
+
id: modelId,
|
|
1418
|
+
index: entryIndex,
|
|
1419
|
+
displayName: descriptor.displayName,
|
|
1420
|
+
baseUrl,
|
|
1421
|
+
apiKey: normalizedApiKey,
|
|
1422
|
+
provider: FACTORY_DROID_ROUTER_PROVIDER
|
|
1423
|
+
};
|
|
1424
|
+
})
|
|
1425
|
+
: [{
|
|
1426
|
+
[FACTORY_DROID_ROUTER_MARKER]: true,
|
|
1427
|
+
model: "llm-router",
|
|
1428
|
+
id: buildFactoryDroidCustomModelId("llm-router", routerEntryStartIndex),
|
|
1429
|
+
index: routerEntryStartIndex,
|
|
1430
|
+
displayName: buildFactoryDroidRouterDisplayName("llm-router", { kind: "alias" }),
|
|
1431
|
+
baseUrl,
|
|
1432
|
+
apiKey: normalizedApiKey,
|
|
1433
|
+
provider: FACTORY_DROID_ROUTER_PROVIDER
|
|
1434
|
+
}];
|
|
1435
|
+
|
|
1436
|
+
customModels.push(...routerEntries);
|
|
1227
1437
|
nextSettings.customModels = customModels;
|
|
1438
|
+
const allCustomModels = nextSettings.customModels;
|
|
1228
1439
|
|
|
1229
1440
|
if (normalizedBindings.defaultModel) {
|
|
1230
|
-
|
|
1231
|
-
|
|
1441
|
+
const selectedModel = resolveFactoryDroidBindingSelectionValue(normalizedBindings.defaultModel, allCustomModels, routeLookup);
|
|
1442
|
+
nextSettings.model = selectedModel;
|
|
1443
|
+
setNestedObjectValue(nextSettings, ["sessionDefaultSettings", "model"], selectedModel);
|
|
1232
1444
|
} else {
|
|
1233
1445
|
delete nextSettings.model;
|
|
1234
1446
|
deleteNestedObjectValue(nextSettings, ["sessionDefaultSettings", "model"]);
|
|
1235
1447
|
}
|
|
1236
1448
|
|
|
1237
1449
|
if (normalizedBindings.missionOrchestratorModel) {
|
|
1238
|
-
nextSettings.missionOrchestratorModel =
|
|
1450
|
+
nextSettings.missionOrchestratorModel = resolveFactoryDroidBindingSelectionValue(
|
|
1451
|
+
normalizedBindings.missionOrchestratorModel,
|
|
1452
|
+
allCustomModels,
|
|
1453
|
+
routeLookup
|
|
1454
|
+
);
|
|
1239
1455
|
} else {
|
|
1240
1456
|
delete nextSettings.missionOrchestratorModel;
|
|
1241
1457
|
}
|
|
1242
1458
|
|
|
1243
1459
|
if (normalizedBindings.missionWorkerModel) {
|
|
1244
|
-
setNestedObjectValue(
|
|
1460
|
+
setNestedObjectValue(
|
|
1461
|
+
nextSettings,
|
|
1462
|
+
["missionModelSettings", "workerModel"],
|
|
1463
|
+
resolveFactoryDroidBindingSelectionValue(normalizedBindings.missionWorkerModel, allCustomModels, routeLookup)
|
|
1464
|
+
);
|
|
1245
1465
|
} else {
|
|
1246
1466
|
deleteNestedObjectValue(nextSettings, ["missionModelSettings", "workerModel"]);
|
|
1247
1467
|
}
|
|
1248
1468
|
|
|
1249
1469
|
if (normalizedBindings.missionValidatorModel) {
|
|
1250
|
-
setNestedObjectValue(
|
|
1470
|
+
setNestedObjectValue(
|
|
1471
|
+
nextSettings,
|
|
1472
|
+
["missionModelSettings", "validationWorkerModel"],
|
|
1473
|
+
resolveFactoryDroidBindingSelectionValue(normalizedBindings.missionValidatorModel, allCustomModels, routeLookup)
|
|
1474
|
+
);
|
|
1251
1475
|
} else {
|
|
1252
1476
|
deleteNestedObjectValue(nextSettings, ["missionModelSettings", "validationWorkerModel"]);
|
|
1253
1477
|
}
|
|
@@ -1265,7 +1489,22 @@ export async function patchFactoryDroidSettingsFile({
|
|
|
1265
1489
|
settingsCreated: !settingsState.existed,
|
|
1266
1490
|
baseUrl,
|
|
1267
1491
|
configuredProvider: FACTORY_DROID_ROUTER_PROVIDER,
|
|
1268
|
-
bindings:
|
|
1492
|
+
bindings: resolvedBindings,
|
|
1493
|
+
bindingIds: normalizeFactoryDroidBindings({
|
|
1494
|
+
defaultModel: normalizedBindings.defaultModel
|
|
1495
|
+
? resolveFactoryDroidBindingSelectionValue(normalizedBindings.defaultModel, allCustomModels, routeLookup)
|
|
1496
|
+
: "",
|
|
1497
|
+
missionOrchestratorModel: normalizedBindings.missionOrchestratorModel
|
|
1498
|
+
? resolveFactoryDroidBindingSelectionValue(normalizedBindings.missionOrchestratorModel, allCustomModels, routeLookup)
|
|
1499
|
+
: "",
|
|
1500
|
+
missionWorkerModel: normalizedBindings.missionWorkerModel
|
|
1501
|
+
? resolveFactoryDroidBindingSelectionValue(normalizedBindings.missionWorkerModel, allCustomModels, routeLookup)
|
|
1502
|
+
: "",
|
|
1503
|
+
missionValidatorModel: normalizedBindings.missionValidatorModel
|
|
1504
|
+
? resolveFactoryDroidBindingSelectionValue(normalizedBindings.missionValidatorModel, allCustomModels, routeLookup)
|
|
1505
|
+
: "",
|
|
1506
|
+
reasoningEffort: normalizedBindings.reasoningEffort
|
|
1507
|
+
})
|
|
1269
1508
|
};
|
|
1270
1509
|
}
|
|
1271
1510
|
|