dsh-llm-fallbacks 0.3.0 → 0.3.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/README.md +60 -24
- package/README.zh-CN.md +58 -22
- package/dist/client/FallbacksCard.d.ts +1 -1
- package/dist/client/GeneralFallbacksRow.d.ts +1 -1
- package/dist/client/index.js +165 -5
- package/dist/client/use-snapshot.d.ts +11 -0
- package/dist/commands.d.ts +120 -27
- package/dist/gateway.d.ts +17 -0
- package/dist/index.js +473 -12
- package/dist/selectors.d.ts +6 -3
- package/dist/tui-settings.d.ts +140 -0
- package/dist/tui.d.ts +5 -4
- package/package.json +26 -23
- package/scripts/build-client.ts +9 -9
package/dist/index.js
CHANGED
|
@@ -21,7 +21,7 @@ function parseSelector(input) {
|
|
|
21
21
|
const provider = trimmed.slice(0, slash).trim();
|
|
22
22
|
const modelPart = trimmed.slice(slash + 1).trim();
|
|
23
23
|
if (!provider || !modelPart) throw new SelectorError(`invalid selector "${input}": empty provider or model`);
|
|
24
|
-
if (modelPart.includes("
|
|
24
|
+
if (modelPart !== "*" && modelPart.includes("*")) throw new SelectorError(`invalid selector "${input}": unexpected wildcard in model`);
|
|
25
25
|
return {
|
|
26
26
|
provider,
|
|
27
27
|
model: modelPart === "*" ? void 0 : modelPart,
|
|
@@ -953,12 +953,17 @@ const CONFIG_SUBCOMMAND_DESCRIPTION = {
|
|
|
953
953
|
zh: "查看组合后的 fallbacks 配置(设置回读)",
|
|
954
954
|
en: "show the composed fallbacks config (settings readback)"
|
|
955
955
|
};
|
|
956
|
+
const REVERT_SEED_SUBCOMMAND_DESCRIPTION = {
|
|
957
|
+
zh: "将角色的 persona 还原为已声明的 Seed 默认",
|
|
958
|
+
en: "revert a role's persona to its declared seed default"
|
|
959
|
+
};
|
|
956
960
|
const FALLBACKS_COMMAND_LOCALES = {
|
|
957
961
|
zh: {
|
|
958
962
|
title: "当前会话 fallback 诊断(只读)",
|
|
959
963
|
description: "查看当前会话的降级链、最近降级切换与冷却状态(只读)",
|
|
960
964
|
usageConfig: CONFIG_SUBCOMMAND_DESCRIPTION.zh,
|
|
961
|
-
|
|
965
|
+
usageRevertSeed: REVERT_SEED_SUBCOMMAND_DESCRIPTION.zh,
|
|
966
|
+
usage: ` /fallbacks config ${CONFIG_SUBCOMMAND_DESCRIPTION.zh}\n /fallbacks config revert-seed <role-id> ${REVERT_SEED_SUBCOMMAND_DESCRIPTION.zh}`,
|
|
962
967
|
origin: "会话来源",
|
|
963
968
|
role: "角色",
|
|
964
969
|
chain: "链",
|
|
@@ -982,22 +987,37 @@ const FALLBACKS_COMMAND_LOCALES = {
|
|
|
982
987
|
configTriggerCodes: "触发码",
|
|
983
988
|
configRootChain: "根链",
|
|
984
989
|
configEmpty: "(空)",
|
|
990
|
+
configTimeSlots: "分时槽",
|
|
991
|
+
configSlotPresetItem: "{preset}(chain: {n}, window {window})",
|
|
992
|
+
configSlotPresetBare: "{preset}(chain: {n})",
|
|
993
|
+
configSlotCustomItem: "custom {start}-{end}(chain: {n})",
|
|
994
|
+
configTz: "时区",
|
|
985
995
|
configRoles: "角色",
|
|
986
996
|
configRoleItem: "{id}(chain: {n})",
|
|
997
|
+
configRules: "角色规则",
|
|
987
998
|
configCooldown: "冷却",
|
|
988
999
|
configRevert: "回主策略",
|
|
989
1000
|
configMaxSwitches: "单步最大切换",
|
|
990
1001
|
configAlwaysCap: "always 上限",
|
|
991
1002
|
configPresets: "预置",
|
|
992
1003
|
configRoleAutoMatch: "角色自动匹配",
|
|
993
|
-
configEdit: "
|
|
994
|
-
configEditHint: "TUI
|
|
1004
|
+
configEdit: "编辑:/settings(TUI 设置界面)或 ~/.dsh/profiles/<profile>/cordis.patch.yml(插件行)/ $DSH_HOME/settings.yaml(fallbacks: 分节)",
|
|
1005
|
+
configEditHint: "TUI 通过 /settings 修改配置;文件编辑仍然可用",
|
|
1006
|
+
revertSeedOk: "角色 {id} 已还原为 Seed 默认",
|
|
1007
|
+
revertSeedFail: "角色 {id} 未还原({reason})",
|
|
1008
|
+
revertSeedError: "角色 {id} 还原失败(设置写入失败)",
|
|
1009
|
+
revertSeedReason: {
|
|
1010
|
+
"not-seeded": "未声明种子",
|
|
1011
|
+
"row-absent": "角色行不存在",
|
|
1012
|
+
"settings-unavailable": "设置通道不可用"
|
|
1013
|
+
}
|
|
995
1014
|
},
|
|
996
1015
|
en: {
|
|
997
1016
|
title: "Session fallback diagnostics (read-only)",
|
|
998
1017
|
description: "Inspect fallback chain, recent fallback switches, and cooldown for this session (read-only)",
|
|
999
1018
|
usageConfig: CONFIG_SUBCOMMAND_DESCRIPTION.en,
|
|
1000
|
-
|
|
1019
|
+
usageRevertSeed: REVERT_SEED_SUBCOMMAND_DESCRIPTION.en,
|
|
1020
|
+
usage: ` /fallbacks config ${CONFIG_SUBCOMMAND_DESCRIPTION.en}\n /fallbacks config revert-seed <role-id> ${REVERT_SEED_SUBCOMMAND_DESCRIPTION.en}`,
|
|
1001
1021
|
origin: "Session origin",
|
|
1002
1022
|
role: "Role",
|
|
1003
1023
|
chain: "Chain",
|
|
@@ -1021,20 +1041,45 @@ const FALLBACKS_COMMAND_LOCALES = {
|
|
|
1021
1041
|
configTriggerCodes: "Trigger codes",
|
|
1022
1042
|
configRootChain: "Root chain",
|
|
1023
1043
|
configEmpty: "(empty)",
|
|
1044
|
+
configTimeSlots: "Time slots",
|
|
1045
|
+
configSlotPresetItem: "{preset} (chain: {n}, window {window})",
|
|
1046
|
+
configSlotPresetBare: "{preset} (chain: {n})",
|
|
1047
|
+
configSlotCustomItem: "custom {start}-{end} (chain: {n})",
|
|
1048
|
+
configTz: "TZ",
|
|
1024
1049
|
configRoles: "Roles",
|
|
1025
1050
|
configRoleItem: "{id} (chain: {n})",
|
|
1051
|
+
configRules: "Rules",
|
|
1026
1052
|
configCooldown: "Cooldown",
|
|
1027
1053
|
configRevert: "Revert",
|
|
1028
1054
|
configMaxSwitches: "Max switches/step",
|
|
1029
1055
|
configAlwaysCap: "Always-mode cap",
|
|
1030
1056
|
configPresets: "Presets",
|
|
1031
1057
|
configRoleAutoMatch: "Auto-match",
|
|
1032
|
-
configEdit: "Edit: ~/.dsh/profiles/<profile>/cordis.patch.yml (plugin row)
|
|
1033
|
-
configEditHint: "TUI
|
|
1058
|
+
configEdit: "Edit: /settings (TUI settings screen) or ~/.dsh/profiles/<profile>/cordis.patch.yml (plugin row) / $DSH_HOME/settings.yaml (fallbacks: section)",
|
|
1059
|
+
configEditHint: "TUI edits config via /settings; file editing still works",
|
|
1060
|
+
revertSeedOk: "role {id} reverted to its seed default",
|
|
1061
|
+
revertSeedFail: "role {id} not reverted ({reason})",
|
|
1062
|
+
revertSeedError: "role {id} revert failed (settings write failed)",
|
|
1063
|
+
revertSeedReason: {
|
|
1064
|
+
"not-seeded": "not a seeded role",
|
|
1065
|
+
"row-absent": "role row absent",
|
|
1066
|
+
"settings-unavailable": "settings channel unavailable"
|
|
1067
|
+
}
|
|
1034
1068
|
}
|
|
1035
1069
|
};
|
|
1036
1070
|
function parseFallbacksSubcommand(rawInput) {
|
|
1037
|
-
|
|
1071
|
+
const trimmed = rawInput.trim();
|
|
1072
|
+
if (trimmed === "config") return { kind: "config" };
|
|
1073
|
+
const [head, sub, ...rest] = trimmed.split(/\s+/);
|
|
1074
|
+
if (head === "config" && sub === "revert-seed") {
|
|
1075
|
+
const arg = rest.join(" ").trim();
|
|
1076
|
+
if (arg === "") return { kind: "" };
|
|
1077
|
+
return {
|
|
1078
|
+
kind: "revert-seed",
|
|
1079
|
+
arg
|
|
1080
|
+
};
|
|
1081
|
+
}
|
|
1082
|
+
return { kind: "" };
|
|
1038
1083
|
}
|
|
1039
1084
|
function isFallbacksSwitchData(data) {
|
|
1040
1085
|
if (typeof data !== "object" || data === null) return false;
|
|
@@ -1090,13 +1135,72 @@ function formatConfigRoles(roles, t) {
|
|
|
1090
1135
|
const list = items.length === 0 ? "" : `${items.join(", ")}${roles.length > 5 ? ", …" : ""}`;
|
|
1091
1136
|
return `${roles.length}${list.length === 0 ? "" : ` — ${list}`}`;
|
|
1092
1137
|
}
|
|
1138
|
+
const DAY_NAMES = [
|
|
1139
|
+
"Sun",
|
|
1140
|
+
"Mon",
|
|
1141
|
+
"Tue",
|
|
1142
|
+
"Wed",
|
|
1143
|
+
"Thu",
|
|
1144
|
+
"Fri",
|
|
1145
|
+
"Sat"
|
|
1146
|
+
];
|
|
1147
|
+
function formatDayMask(days) {
|
|
1148
|
+
if (days.length === 0) return "";
|
|
1149
|
+
const sorted = days.filter((day) => Number.isInteger(day) && day >= 0 && day <= 6).sort((a, b) => a - b);
|
|
1150
|
+
if (sorted.length === 0) return "";
|
|
1151
|
+
const runs = [];
|
|
1152
|
+
let runStart = sorted[0];
|
|
1153
|
+
let prev = sorted[0];
|
|
1154
|
+
for (let index = 1; index <= sorted.length; index += 1) {
|
|
1155
|
+
const day = sorted[index];
|
|
1156
|
+
if (day === void 0 || day !== prev + 1) {
|
|
1157
|
+
runs.push(prev === runStart ? DAY_NAMES[runStart] : `${DAY_NAMES[runStart]}-${DAY_NAMES[prev]}`);
|
|
1158
|
+
runStart = day;
|
|
1159
|
+
prev = day;
|
|
1160
|
+
} else prev = day;
|
|
1161
|
+
}
|
|
1162
|
+
return runs.join(", ");
|
|
1163
|
+
}
|
|
1164
|
+
function formatDayMaskSegment(days) {
|
|
1165
|
+
const mask = formatDayMask(days ?? []);
|
|
1166
|
+
return mask === "" ? "" : ` (${mask})`;
|
|
1167
|
+
}
|
|
1168
|
+
function formatSlotWindow(preset) {
|
|
1169
|
+
const definition = PRESETS[preset];
|
|
1170
|
+
if (definition === void 0) return "";
|
|
1171
|
+
const windows = definition.windows.map((window) => {
|
|
1172
|
+
const dayMask = formatDayMaskSegment(window.days);
|
|
1173
|
+
return `${window.start}-${window.end}${dayMask}`;
|
|
1174
|
+
}).join(", ");
|
|
1175
|
+
return definition.complement ? `outside ${windows}` : windows;
|
|
1176
|
+
}
|
|
1177
|
+
function formatConfigTimeSlots(slots, t) {
|
|
1178
|
+
const items = slots.slice(0, 5).map((row) => {
|
|
1179
|
+
if (row.preset !== void 0) {
|
|
1180
|
+
const window = formatSlotWindow(row.preset);
|
|
1181
|
+
return window === "" ? t.configSlotPresetBare.replace("{preset}", row.preset).replace("{n}", String(row.chainCount)) : t.configSlotPresetItem.replace("{preset}", row.preset).replace("{n}", String(row.chainCount)).replace("{window}", window);
|
|
1182
|
+
}
|
|
1183
|
+
const bounds = row.start !== void 0 && row.end !== void 0 ? `${row.start}-${row.end}${formatDayMaskSegment(row.days)}` : "";
|
|
1184
|
+
return t.configSlotCustomItem.replace("{start}-{end}", bounds).replace("{n}", String(row.chainCount));
|
|
1185
|
+
});
|
|
1186
|
+
const list = items.length === 0 ? "" : `${items.join(", ")}${slots.length > 5 ? ", …" : ""}`;
|
|
1187
|
+
return `${slots.length}${list.length === 0 ? "" : ` — ${list}`}`;
|
|
1188
|
+
}
|
|
1189
|
+
function formatConfigRules(rules, t) {
|
|
1190
|
+
const items = rules.slice(0, 5).map((rule) => `${rule.provider === "" ? "*" : rule.provider}/${rule.model === "" ? "*" : rule.model} → ${rule.role}`);
|
|
1191
|
+
const list = items.length === 0 ? "" : `${items.join(", ")}${rules.length > 5 ? ", …" : ""}`;
|
|
1192
|
+
return `${rules.length}${list.length === 0 ? "" : ` — ${list}`}`;
|
|
1193
|
+
}
|
|
1093
1194
|
function fallbacksConfigText(summary, locale = "zh") {
|
|
1094
1195
|
const t = FALLBACKS_COMMAND_LOCALES[locale];
|
|
1095
1196
|
return [
|
|
1096
1197
|
`${t.configTitle}: ${summary.enabled ? t.configEnabled : t.configDisabled}`,
|
|
1097
1198
|
`${t.configTriggerCodes}: ${summary.triggerCodes.length === 0 ? t.configEmpty : formatConfigList(summary.triggerCodes)}`,
|
|
1098
1199
|
`${t.configRootChain}: ${summary.rootChain.length === 0 ? t.configEmpty : formatConfigList(summary.rootChain)}`,
|
|
1200
|
+
`${t.configTimeSlots}: ${summary.timeSlots.length === 0 ? t.configEmpty : formatConfigTimeSlots(summary.timeSlots, t)}`,
|
|
1201
|
+
`${t.configTz}: ${summary.tz}`,
|
|
1099
1202
|
`${t.configRoles}: ${formatConfigRoles(summary.roles, t)}`,
|
|
1203
|
+
`${t.configRules}: ${summary.rules.length === 0 ? t.configEmpty : formatConfigRules(summary.rules, t)}`,
|
|
1100
1204
|
`${t.configCooldown}: ${summary.cooldownMs} ms`,
|
|
1101
1205
|
`${t.configRevert}: ${summary.revertPolicy}`,
|
|
1102
1206
|
`${t.configMaxSwitches}: ${summary.maxSwitchesPerStep}`,
|
|
@@ -1132,10 +1236,31 @@ function fallbacksCommandText(snapshot, locale = "zh") {
|
|
|
1132
1236
|
return lines.join("\n");
|
|
1133
1237
|
}
|
|
1134
1238
|
function createFallbacksCommandHandler(controller, locale = "zh") {
|
|
1135
|
-
return (invocation) =>
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1239
|
+
return (invocation) => {
|
|
1240
|
+
const parsed = parseFallbacksSubcommand(invocation.rawInput ?? "");
|
|
1241
|
+
if (parsed.kind === "config") return {
|
|
1242
|
+
kind: "success",
|
|
1243
|
+
text: fallbacksConfigText(controller.getConfig(), locale)
|
|
1244
|
+
};
|
|
1245
|
+
if (parsed.kind === "revert-seed" && parsed.arg !== void 0) {
|
|
1246
|
+
const roleId = parsed.arg;
|
|
1247
|
+
const t = FALLBACKS_COMMAND_LOCALES[locale];
|
|
1248
|
+
return controller.revertSeed(roleId).then((outcome) => outcome.ok ? {
|
|
1249
|
+
kind: "success",
|
|
1250
|
+
text: t.revertSeedOk.replace("{id}", roleId)
|
|
1251
|
+
} : {
|
|
1252
|
+
kind: "error",
|
|
1253
|
+
text: t.revertSeedFail.replace("{id}", roleId).replace("{reason}", t.revertSeedReason[outcome.reason ?? "not-seeded"])
|
|
1254
|
+
}).catch(() => ({
|
|
1255
|
+
kind: "error",
|
|
1256
|
+
text: t.revertSeedError.replace("{id}", roleId)
|
|
1257
|
+
}));
|
|
1258
|
+
}
|
|
1259
|
+
return {
|
|
1260
|
+
kind: "success",
|
|
1261
|
+
text: fallbacksCommandText(controller.getSnapshot(invocation.agent), locale)
|
|
1262
|
+
};
|
|
1263
|
+
};
|
|
1139
1264
|
}
|
|
1140
1265
|
function registerFallbacksCommands(registry, controller, locale = "zh") {
|
|
1141
1266
|
return registry.register({
|
|
@@ -1369,9 +1494,18 @@ const FALLBACKS_CONFIG_NODE = {
|
|
|
1369
1494
|
en: FALLBACKS_COMMAND_LOCALES.en.usageConfig
|
|
1370
1495
|
}
|
|
1371
1496
|
};
|
|
1497
|
+
const REVERT_SEED_NODE = {
|
|
1498
|
+
name: "revert-seed",
|
|
1499
|
+
description: FALLBACKS_COMMAND_LOCALES.zh.usageRevertSeed,
|
|
1500
|
+
descriptions: {
|
|
1501
|
+
zh: FALLBACKS_COMMAND_LOCALES.zh.usageRevertSeed,
|
|
1502
|
+
en: FALLBACKS_COMMAND_LOCALES.en.usageRevertSeed
|
|
1503
|
+
}
|
|
1504
|
+
};
|
|
1372
1505
|
function fallbacksChildren(canonicalPath) {
|
|
1373
1506
|
if (canonicalPath[0] !== "fallbacks") return [];
|
|
1374
1507
|
if (canonicalPath.length === 1) return [FALLBACKS_CONFIG_NODE];
|
|
1508
|
+
if (canonicalPath.length === 2 && canonicalPath[1] === "config") return [REVERT_SEED_NODE];
|
|
1375
1509
|
return [];
|
|
1376
1510
|
}
|
|
1377
1511
|
const FALLBACKS_PROVIDER$1 = {
|
|
@@ -1397,6 +1531,309 @@ function installTuiClient(ctx, opts) {
|
|
|
1397
1531
|
});
|
|
1398
1532
|
}
|
|
1399
1533
|
//#endregion
|
|
1534
|
+
//#region .build/host/tui-settings.js
|
|
1535
|
+
const FALLBACKS_TUI_SECTION_NS = "fallbacks";
|
|
1536
|
+
function jsonFieldFormat(value) {
|
|
1537
|
+
return value === void 0 || value === null ? "" : JSON.stringify(value, null, 2);
|
|
1538
|
+
}
|
|
1539
|
+
function jsonFieldParse(patch) {
|
|
1540
|
+
return (text) => {
|
|
1541
|
+
if (text.trim() === "") return { kind: "clear" };
|
|
1542
|
+
if (new TextEncoder().encode(text).length > 65536) return void 0;
|
|
1543
|
+
let parsed;
|
|
1544
|
+
try {
|
|
1545
|
+
parsed = JSON.parse(text);
|
|
1546
|
+
} catch {
|
|
1547
|
+
return;
|
|
1548
|
+
}
|
|
1549
|
+
if (parsed === null) return { kind: "clear" };
|
|
1550
|
+
try {
|
|
1551
|
+
validateConfigPatch(patch(parsed));
|
|
1552
|
+
} catch {
|
|
1553
|
+
return;
|
|
1554
|
+
}
|
|
1555
|
+
return {
|
|
1556
|
+
kind: "set",
|
|
1557
|
+
value: parsed
|
|
1558
|
+
};
|
|
1559
|
+
};
|
|
1560
|
+
}
|
|
1561
|
+
function triggerCodesFormat(value) {
|
|
1562
|
+
return Array.isArray(value) ? value.join(", ") : "";
|
|
1563
|
+
}
|
|
1564
|
+
function triggerCodesParse(text) {
|
|
1565
|
+
if (text.trim() === "") return { kind: "clear" };
|
|
1566
|
+
const tokens = text.split(",").map((token) => token.trim()).filter((token) => token !== "");
|
|
1567
|
+
try {
|
|
1568
|
+
validateConfigPatch({ triggerCodes: tokens });
|
|
1569
|
+
} catch {
|
|
1570
|
+
return;
|
|
1571
|
+
}
|
|
1572
|
+
return {
|
|
1573
|
+
kind: "set",
|
|
1574
|
+
value: tokens
|
|
1575
|
+
};
|
|
1576
|
+
}
|
|
1577
|
+
function tzParse(text) {
|
|
1578
|
+
const trimmed = text.trim();
|
|
1579
|
+
if (trimmed === "") return { kind: "clear" };
|
|
1580
|
+
try {
|
|
1581
|
+
Intl.DateTimeFormat(void 0, { timeZone: trimmed });
|
|
1582
|
+
} catch {
|
|
1583
|
+
return;
|
|
1584
|
+
}
|
|
1585
|
+
return {
|
|
1586
|
+
kind: "set",
|
|
1587
|
+
value: trimmed
|
|
1588
|
+
};
|
|
1589
|
+
}
|
|
1590
|
+
const BUNDLED_PRESETS_LABEL = `Bundled (${presetRoles.length} preset roles)`;
|
|
1591
|
+
function buildFallbacksTuiSection() {
|
|
1592
|
+
return {
|
|
1593
|
+
ns: FALLBACKS_TUI_SECTION_NS,
|
|
1594
|
+
title: "fallbacks",
|
|
1595
|
+
descriptions: {
|
|
1596
|
+
zh: "回退设置:降级链、分时切换、角色与高级选项(与 Web 设置卡片完全一致)。",
|
|
1597
|
+
en: "Fallback settings: degradation chains, time slots, roles, and advanced options (full parity with the web settings card)."
|
|
1598
|
+
},
|
|
1599
|
+
groups: [
|
|
1600
|
+
{
|
|
1601
|
+
id: "general",
|
|
1602
|
+
title: "General",
|
|
1603
|
+
descriptions: {
|
|
1604
|
+
zh: "通用",
|
|
1605
|
+
en: "General"
|
|
1606
|
+
}
|
|
1607
|
+
},
|
|
1608
|
+
{
|
|
1609
|
+
id: "chain",
|
|
1610
|
+
title: "Fallback chain",
|
|
1611
|
+
descriptions: {
|
|
1612
|
+
zh: "降级链",
|
|
1613
|
+
en: "Fallback chain"
|
|
1614
|
+
}
|
|
1615
|
+
},
|
|
1616
|
+
{
|
|
1617
|
+
id: "roles",
|
|
1618
|
+
title: "Roles",
|
|
1619
|
+
descriptions: {
|
|
1620
|
+
zh: "角色",
|
|
1621
|
+
en: "Roles"
|
|
1622
|
+
}
|
|
1623
|
+
},
|
|
1624
|
+
{
|
|
1625
|
+
id: "advanced",
|
|
1626
|
+
title: "Advanced",
|
|
1627
|
+
descriptions: {
|
|
1628
|
+
zh: "高级",
|
|
1629
|
+
en: "Advanced"
|
|
1630
|
+
}
|
|
1631
|
+
}
|
|
1632
|
+
],
|
|
1633
|
+
fields: [
|
|
1634
|
+
{
|
|
1635
|
+
path: ["enabled"],
|
|
1636
|
+
label: "Enabled",
|
|
1637
|
+
descriptions: {
|
|
1638
|
+
zh: "启用回退",
|
|
1639
|
+
en: "Enabled"
|
|
1640
|
+
},
|
|
1641
|
+
group: "general",
|
|
1642
|
+
kind: "boolean"
|
|
1643
|
+
},
|
|
1644
|
+
{
|
|
1645
|
+
path: ["roleAutoMatch"],
|
|
1646
|
+
label: "LLM role auto-match",
|
|
1647
|
+
descriptions: {
|
|
1648
|
+
zh: "LLM 角色自动匹配",
|
|
1649
|
+
en: "LLM role auto-match"
|
|
1650
|
+
},
|
|
1651
|
+
group: "general",
|
|
1652
|
+
kind: "boolean"
|
|
1653
|
+
},
|
|
1654
|
+
{
|
|
1655
|
+
path: ["presets"],
|
|
1656
|
+
label: "Preset roles",
|
|
1657
|
+
descriptions: {
|
|
1658
|
+
zh: "预置角色",
|
|
1659
|
+
en: "Preset roles"
|
|
1660
|
+
},
|
|
1661
|
+
group: "general",
|
|
1662
|
+
kind: "select",
|
|
1663
|
+
options: [{
|
|
1664
|
+
value: "bundled",
|
|
1665
|
+
label: BUNDLED_PRESETS_LABEL,
|
|
1666
|
+
descriptions: {
|
|
1667
|
+
zh: `预置(${presetRoles.length} 个预置角色)`,
|
|
1668
|
+
en: BUNDLED_PRESETS_LABEL
|
|
1669
|
+
}
|
|
1670
|
+
}, {
|
|
1671
|
+
value: "none",
|
|
1672
|
+
label: "None",
|
|
1673
|
+
descriptions: {
|
|
1674
|
+
zh: "不注入",
|
|
1675
|
+
en: "None"
|
|
1676
|
+
}
|
|
1677
|
+
}]
|
|
1678
|
+
},
|
|
1679
|
+
{
|
|
1680
|
+
path: ["triggerCodes"],
|
|
1681
|
+
label: "Trigger codes",
|
|
1682
|
+
descriptions: {
|
|
1683
|
+
zh: "触发码",
|
|
1684
|
+
en: "Trigger codes"
|
|
1685
|
+
},
|
|
1686
|
+
hint: "Comma-separated failure codes that trigger the fallback. A code containing a comma cannot round-trip through this field.",
|
|
1687
|
+
hintDescriptions: {
|
|
1688
|
+
zh: "逗号分隔的触发回退的失败码;包含逗号的代码无法通过该字段往返。",
|
|
1689
|
+
en: "Comma-separated failure codes that trigger the fallback. A code containing a comma cannot round-trip through this field."
|
|
1690
|
+
},
|
|
1691
|
+
group: "general",
|
|
1692
|
+
kind: "text",
|
|
1693
|
+
format: triggerCodesFormat,
|
|
1694
|
+
parse: triggerCodesParse
|
|
1695
|
+
},
|
|
1696
|
+
{
|
|
1697
|
+
path: ["tz"],
|
|
1698
|
+
label: "Timezone",
|
|
1699
|
+
descriptions: {
|
|
1700
|
+
zh: "时区",
|
|
1701
|
+
en: "Timezone"
|
|
1702
|
+
},
|
|
1703
|
+
hint: "IANA timezone identifier, e.g. Asia/Shanghai.",
|
|
1704
|
+
hintDescriptions: {
|
|
1705
|
+
zh: "IANA 时区标识符,如 Asia/Shanghai。",
|
|
1706
|
+
en: "IANA timezone identifier, e.g. Asia/Shanghai."
|
|
1707
|
+
},
|
|
1708
|
+
group: "general",
|
|
1709
|
+
kind: "text",
|
|
1710
|
+
parse: tzParse
|
|
1711
|
+
},
|
|
1712
|
+
{
|
|
1713
|
+
path: ["rootChain"],
|
|
1714
|
+
label: "Root chain (JSON)",
|
|
1715
|
+
descriptions: {
|
|
1716
|
+
zh: "根降级链(JSON)",
|
|
1717
|
+
en: "Root chain (JSON)"
|
|
1718
|
+
},
|
|
1719
|
+
hint: "JSON array of model selectors; the last entry must be an official V4 model.",
|
|
1720
|
+
hintDescriptions: {
|
|
1721
|
+
zh: "模型选择器 JSON 数组;末项必须是官方 V4 模型。",
|
|
1722
|
+
en: "JSON array of model selectors; the last entry must be an official V4 model."
|
|
1723
|
+
},
|
|
1724
|
+
group: "chain",
|
|
1725
|
+
kind: "text",
|
|
1726
|
+
format: jsonFieldFormat,
|
|
1727
|
+
parse: jsonFieldParse((parsed) => ({ rootChain: parsed }))
|
|
1728
|
+
},
|
|
1729
|
+
{
|
|
1730
|
+
path: ["timeSlots"],
|
|
1731
|
+
label: "Time slots (JSON)",
|
|
1732
|
+
descriptions: {
|
|
1733
|
+
zh: "分时切换(JSON)",
|
|
1734
|
+
en: "Time slots (JSON)"
|
|
1735
|
+
},
|
|
1736
|
+
hint: "JSON array of slot rows (kind: preset|custom).",
|
|
1737
|
+
hintDescriptions: {
|
|
1738
|
+
zh: "分时行 JSON 数组(kind: preset|custom)。",
|
|
1739
|
+
en: "JSON array of slot rows (kind: preset|custom)."
|
|
1740
|
+
},
|
|
1741
|
+
group: "chain",
|
|
1742
|
+
kind: "text",
|
|
1743
|
+
format: jsonFieldFormat,
|
|
1744
|
+
parse: jsonFieldParse((parsed) => ({ timeSlots: parsed }))
|
|
1745
|
+
},
|
|
1746
|
+
{
|
|
1747
|
+
path: ["roles", "list"],
|
|
1748
|
+
label: "Roles (JSON)",
|
|
1749
|
+
descriptions: {
|
|
1750
|
+
zh: "角色列表(JSON)",
|
|
1751
|
+
en: "Roles (JSON)"
|
|
1752
|
+
},
|
|
1753
|
+
group: "roles",
|
|
1754
|
+
kind: "text",
|
|
1755
|
+
format: jsonFieldFormat,
|
|
1756
|
+
parse: jsonFieldParse((parsed) => ({ roles: { list: parsed } }))
|
|
1757
|
+
},
|
|
1758
|
+
{
|
|
1759
|
+
path: ["roles", "rules"],
|
|
1760
|
+
label: "Role rules (JSON)",
|
|
1761
|
+
descriptions: {
|
|
1762
|
+
zh: "角色规则(JSON)",
|
|
1763
|
+
en: "Role rules (JSON)"
|
|
1764
|
+
},
|
|
1765
|
+
group: "roles",
|
|
1766
|
+
kind: "text",
|
|
1767
|
+
format: jsonFieldFormat,
|
|
1768
|
+
parse: jsonFieldParse((parsed) => ({ roles: { rules: parsed } }))
|
|
1769
|
+
},
|
|
1770
|
+
{
|
|
1771
|
+
path: ["cooldownMs"],
|
|
1772
|
+
label: "Cooldown (ms)",
|
|
1773
|
+
descriptions: {
|
|
1774
|
+
zh: "冷却时间(毫秒)",
|
|
1775
|
+
en: "Cooldown (ms)"
|
|
1776
|
+
},
|
|
1777
|
+
group: "advanced",
|
|
1778
|
+
kind: "number"
|
|
1779
|
+
},
|
|
1780
|
+
{
|
|
1781
|
+
path: ["maxSwitchesPerStep"],
|
|
1782
|
+
label: "Max switches per step",
|
|
1783
|
+
descriptions: {
|
|
1784
|
+
zh: "单步最大切换次数",
|
|
1785
|
+
en: "Max switches per step"
|
|
1786
|
+
},
|
|
1787
|
+
group: "advanced",
|
|
1788
|
+
kind: "number"
|
|
1789
|
+
},
|
|
1790
|
+
{
|
|
1791
|
+
path: ["alwaysModeRetryCap"],
|
|
1792
|
+
label: "Always-mode retry cap",
|
|
1793
|
+
descriptions: {
|
|
1794
|
+
zh: "Always 模式重试上限",
|
|
1795
|
+
en: "Always-mode retry cap"
|
|
1796
|
+
},
|
|
1797
|
+
group: "advanced",
|
|
1798
|
+
kind: "number"
|
|
1799
|
+
},
|
|
1800
|
+
{
|
|
1801
|
+
path: ["revertPolicy"],
|
|
1802
|
+
label: "Revert policy",
|
|
1803
|
+
descriptions: {
|
|
1804
|
+
zh: "恢复策略",
|
|
1805
|
+
en: "Revert policy"
|
|
1806
|
+
},
|
|
1807
|
+
group: "advanced",
|
|
1808
|
+
kind: "select",
|
|
1809
|
+
options: [{
|
|
1810
|
+
value: "cooldown-expiry",
|
|
1811
|
+
label: "Cooldown expiry",
|
|
1812
|
+
descriptions: {
|
|
1813
|
+
zh: "冷却到期",
|
|
1814
|
+
en: "Cooldown expiry"
|
|
1815
|
+
}
|
|
1816
|
+
}, {
|
|
1817
|
+
value: "never",
|
|
1818
|
+
label: "Never",
|
|
1819
|
+
descriptions: {
|
|
1820
|
+
zh: "从不",
|
|
1821
|
+
en: "Never"
|
|
1822
|
+
}
|
|
1823
|
+
}]
|
|
1824
|
+
}
|
|
1825
|
+
]
|
|
1826
|
+
};
|
|
1827
|
+
}
|
|
1828
|
+
function installTuiSettingsSection(ctx, opts) {
|
|
1829
|
+
if (!opts.serviceOwned) return;
|
|
1830
|
+
ctx.inject(["tuiSettingsSections"], (tctx) => {
|
|
1831
|
+
const registry = tctx.tuiSettingsSections;
|
|
1832
|
+
if (registry === void 0) return;
|
|
1833
|
+
return registry.register(buildFallbacksTuiSection());
|
|
1834
|
+
});
|
|
1835
|
+
}
|
|
1836
|
+
//#endregion
|
|
1400
1837
|
//#region .build/host/virtual-adapter.js
|
|
1401
1838
|
const FALLBACKS_PROVIDER = "FallbacksChain";
|
|
1402
1839
|
const FALLBACKS_CHAIN_MODEL = "Auto";
|
|
@@ -1836,10 +2273,25 @@ function apply(ctx, config = defaultFallbacksConfig) {
|
|
|
1836
2273
|
enabled: config.enabled,
|
|
1837
2274
|
triggerCodes: config.triggerCodes,
|
|
1838
2275
|
rootChain: config.rootChain,
|
|
2276
|
+
timeSlots: (config.timeSlots ?? []).map((row) => row.kind === "preset" ? {
|
|
2277
|
+
preset: row.preset,
|
|
2278
|
+
chainCount: (row.chain ?? []).length
|
|
2279
|
+
} : {
|
|
2280
|
+
start: row.start,
|
|
2281
|
+
end: row.end,
|
|
2282
|
+
days: row.days,
|
|
2283
|
+
chainCount: (row.chain ?? []).length
|
|
2284
|
+
}),
|
|
2285
|
+
tz: config.tz ?? "Asia/Shanghai",
|
|
1839
2286
|
roles: config.roles.list.map((role) => ({
|
|
1840
2287
|
id: role.id,
|
|
1841
2288
|
chainCount: role.chain?.length ?? 0
|
|
1842
2289
|
})),
|
|
2290
|
+
rules: config.roles.rules.map((rule) => ({
|
|
2291
|
+
provider: rule.provider ?? "",
|
|
2292
|
+
model: rule.model ?? "",
|
|
2293
|
+
role: rule.role
|
|
2294
|
+
})),
|
|
1843
2295
|
cooldownMs: config.cooldownMs,
|
|
1844
2296
|
revertPolicy: config.revertPolicy,
|
|
1845
2297
|
maxSwitchesPerStep: config.maxSwitchesPerStep,
|
|
@@ -1847,12 +2299,21 @@ function apply(ctx, config = defaultFallbacksConfig) {
|
|
|
1847
2299
|
presets: config.presets ?? "bundled",
|
|
1848
2300
|
roleAutoMatch: config.roleAutoMatch ?? true
|
|
1849
2301
|
};
|
|
2302
|
+
},
|
|
2303
|
+
revertSeed: async (roleId) => {
|
|
2304
|
+
const outcome = await seeds.revert(roleId, seedsIo);
|
|
2305
|
+
if (outcome.reverted) return { ok: true };
|
|
2306
|
+
return {
|
|
2307
|
+
ok: false,
|
|
2308
|
+
reason: outcome.reason ?? "not-seeded"
|
|
2309
|
+
};
|
|
1850
2310
|
}
|
|
1851
2311
|
};
|
|
1852
2312
|
ctx.inject(["commands"], (commandCtx) => {
|
|
1853
2313
|
return registerFallbacksCommands(commandCtx.commands, fallbacksCommandController);
|
|
1854
2314
|
});
|
|
1855
2315
|
installTuiClient(ctx, { serviceOwned });
|
|
2316
|
+
installTuiSettingsSection(ctx, { serviceOwned });
|
|
1856
2317
|
ctx.inject(["settings"], () => {
|
|
1857
2318
|
if (!serviceOwned) return;
|
|
1858
2319
|
if (seedsIo.read().presets === "none") return;
|
package/dist/selectors.d.ts
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Selector parsing for `fallbacks` chains (spec §4, plan Task 2).
|
|
3
3
|
*
|
|
4
|
-
* Grammar: `provider/model` (exact
|
|
5
|
-
*
|
|
4
|
+
* Grammar: `provider/model` (exact — the model segment may itself contain
|
|
5
|
+
* `/`, e.g. NVIDIA NIM `nvidia/minimaxai/minimax-m3`) and `provider/*`
|
|
6
|
+
* (wildcard — the parsed `model` is `undefined`; `*` is only valid as the
|
|
7
|
+
* entire model segment). Illegal selectors throw {@link SelectorError} —
|
|
6
8
|
* the catchable "config warning" path; warn-and-continue lives in Task 3.
|
|
7
9
|
* These modules never crash on their own.
|
|
8
10
|
*
|
|
@@ -25,7 +27,8 @@ export declare function selectorKey(provider: string, model?: string): string;
|
|
|
25
27
|
* Parse a chain key or entry selector.
|
|
26
28
|
*
|
|
27
29
|
* Accepts `provider/model` and `provider/*`; throws {@link SelectorError}
|
|
28
|
-
* on anything else (missing separator, empty parts,
|
|
30
|
+
* on anything else (missing separator, empty parts, wildcard inside the
|
|
31
|
+
* model segment).
|
|
29
32
|
*/
|
|
30
33
|
export declare function parseSelector(input: string): Selector;
|
|
31
34
|
/**
|