@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 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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@khanglvm/llm-router",
3
- "version": "2.3.4",
3
+ "version": "2.3.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",
@@ -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 && typeof entry === "object" && entry[FACTORY_DROID_ROUTER_MARKER] === true
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 stripRouterManagedCustomModels(customModels) {
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 && typeof entry === "object" && entry[FACTORY_DROID_ROUTER_MARKER] === true)
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: getNestedObjectValue(settingsState.data, ["sessionDefaultSettings", "model"])
1167
- || settingsState.data?.model
1168
- || routerEntry?.model
1169
- || "",
1170
- missionOrchestratorModel: settingsState.data?.missionOrchestratorModel || "",
1171
- missionWorkerModel: getNestedObjectValue(settingsState.data, ["missionModelSettings", "workerModel"]) || "",
1172
- missionValidatorModel: getNestedObjectValue(settingsState.data, ["missionModelSettings", "validationWorkerModel"]) || "",
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 customModels = stripRouterManagedCustomModels(nextSettings.customModels);
1213
- const routerEntry = {
1214
- [FACTORY_DROID_ROUTER_MARKER]: true,
1215
- model: normalizedBindings.defaultModel
1216
- || normalizedBindings.missionOrchestratorModel
1217
- || normalizedBindings.missionWorkerModel
1218
- || normalizedBindings.missionValidatorModel
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.push(routerEntry);
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
- nextSettings.model = normalizedBindings.defaultModel;
1231
- setNestedObjectValue(nextSettings, ["sessionDefaultSettings", "model"], normalizedBindings.defaultModel);
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 = normalizedBindings.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(nextSettings, ["missionModelSettings", "workerModel"], normalizedBindings.missionWorkerModel);
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(nextSettings, ["missionModelSettings", "validationWorkerModel"], normalizedBindings.missionValidatorModel);
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: normalizedBindings
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