mslxdff 0.1.66 → 0.1.67
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/package.json +1 -1
- package/src/chat/cooling.js +99 -0
- package/src/chat/direct.js +57 -0
- package/src/chat/gateway.js +148 -0
- package/src/chat/orchestrator.js +218 -0
- package/src/chat/sse.js +69 -0
- package/src/chat/upstream.js +59 -501
- package/src/cli/bootstrap.js +1 -408
- package/src/cli/provider-row.js +73 -0
- package/src/cli/status.js +3 -52
- package/src/providers/dispatcher.js +18 -6
- package/src/providers/generic.js +2 -0
- package/src/providers/openrouter.js +16 -72
- package/src/providers/workbuddy/auth.js +175 -0
- package/src/providers/workbuddy/balance.js +84 -0
- package/src/providers/workbuddy/chat.js +310 -0
- package/src/providers/workbuddy/index.js +263 -0
- package/src/providers/workbuddy/models.js +111 -0
- package/src/providers/workbuddy/rotation-log.js +54 -0
- package/src/providers/workbuddy.js +2 -652
- package/src/routes/chat/broadband-handler.js +25 -46
- package/src/routes/chat/exhausted-handler.js +2 -2
- package/src/routes/chat/hedge-handler.js +65 -83
- package/src/routes/chat/local-handler.js +32 -61
- package/src/routes/chat/peer-handler.js +32 -23
- package/src/routes/chat/relay-pipeline.js +151 -0
- package/src/runtime/bootstrap.js +408 -0
- package/src/state/memory.js +2 -21
- package/src/state/merge.js +26 -0
- package/src/state/provider-config.js +143 -0
- package/src/state/schemas/provider.js +83 -133
- package/src/state/store.js +2 -28
|
@@ -1,68 +1,43 @@
|
|
|
1
1
|
import { defaultStateFile, readState, writeStateImmediate } from "../store.js";
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
if (!s) return "";
|
|
29
|
-
const idx = s.indexOf("/");
|
|
30
|
-
if (idx > 0) {
|
|
31
|
-
const head = s.slice(0, idx).toLowerCase();
|
|
32
|
-
if (head === String(providerId || "").toLowerCase()) s = s.slice(idx + 1);
|
|
33
|
-
}
|
|
34
|
-
return s;
|
|
35
|
-
}
|
|
36
|
-
export function normalizeEndpointPath(v) {
|
|
37
|
-
const s = String(v || "").trim();
|
|
38
|
-
if (!s) return "";
|
|
39
|
-
return s.startsWith("/") ? s : `/${s}`;
|
|
40
|
-
}
|
|
41
|
-
export function defaultModelsPath(id) {
|
|
42
|
-
if (String(id).toLowerCase() === "workbuddy") return "/console/enterprises/personal/models";
|
|
43
|
-
return "/models";
|
|
44
|
-
}
|
|
45
|
-
export function defaultChatPath(id) {
|
|
46
|
-
if (String(id).toLowerCase() === "workbuddy") return "/v2/chat/completions";
|
|
47
|
-
return "/chat/completions";
|
|
48
|
-
}
|
|
2
|
+
export {
|
|
3
|
+
WORKBUDDY_DEFAULT_BASE_URL,
|
|
4
|
+
normalizeBaseUrl,
|
|
5
|
+
normalizeAuths,
|
|
6
|
+
normalizeAllowedModel,
|
|
7
|
+
normalizeEndpointPath,
|
|
8
|
+
defaultModelsPath,
|
|
9
|
+
defaultChatPath,
|
|
10
|
+
providerKeyEnv,
|
|
11
|
+
providerBaseUrlEnv,
|
|
12
|
+
providerShareEnv,
|
|
13
|
+
} from "../provider-config.js";
|
|
14
|
+
import {
|
|
15
|
+
normalizeBaseUrl as _normalizeBaseUrl,
|
|
16
|
+
normalizeAuths as _normalizeAuths,
|
|
17
|
+
normalizeAllowedModel as _normalizeAllowedModel,
|
|
18
|
+
normalizeEndpointPath as _normalizeEndpointPath,
|
|
19
|
+
defaultModelsPath as _defaultModelsPath,
|
|
20
|
+
defaultChatPath as _defaultChatPath,
|
|
21
|
+
providerKeyEnv as _providerKeyEnv,
|
|
22
|
+
providerBaseUrlEnv as _providerBaseUrlEnv,
|
|
23
|
+
providerShareEnv as _providerShareEnv,
|
|
24
|
+
WORKBUDDY_DEFAULT_BASE_URL as _WORKBUDDY,
|
|
25
|
+
} from "../provider-config.js";
|
|
26
|
+
|
|
27
|
+
// 薄门面:所有 load/save 仍在此,但归一化逻辑已收至 provider-config 单点
|
|
49
28
|
export function loadProviderModelsPath(id, { file = defaultStateFile() } = {}) {
|
|
50
|
-
const cfg =
|
|
51
|
-
if (cfg && typeof cfg.modelsPath === "string" && cfg.modelsPath.trim()) return
|
|
52
|
-
return
|
|
29
|
+
const cfg = readState(file).providerConfigs?.[id];
|
|
30
|
+
if (cfg && typeof cfg.modelsPath === "string" && cfg.modelsPath.trim()) return _normalizeEndpointPath(cfg.modelsPath);
|
|
31
|
+
return _defaultModelsPath(id);
|
|
53
32
|
}
|
|
54
33
|
export function loadProviderChatPath(id, { file = defaultStateFile() } = {}) {
|
|
55
|
-
const cfg =
|
|
56
|
-
if (cfg && typeof cfg.chatPath === "string" && cfg.chatPath.trim()) return
|
|
57
|
-
return
|
|
34
|
+
const cfg = readState(file).providerConfigs?.[id];
|
|
35
|
+
if (cfg && typeof cfg.chatPath === "string" && cfg.chatPath.trim()) return _normalizeEndpointPath(cfg.chatPath);
|
|
36
|
+
return _defaultChatPath(id);
|
|
58
37
|
}
|
|
59
38
|
|
|
60
|
-
export const providerKeyEnv = (id) => `MSLXDFF_${String(id || "").toUpperCase().replace(/[^A-Z0-9]/g, "_")}_KEY`;
|
|
61
|
-
export const providerBaseUrlEnv = (id) => `MSLXDFF_${String(id || "").toUpperCase().replace(/[^A-Z0-9]/g, "_")}_BASE_URL`;
|
|
62
|
-
export const providerShareEnv = (id) => `MSLXDFF_${String(id || "").toUpperCase().replace(/[^A-Z0-9]/g, "_")}_SHARE_KEYS`;
|
|
63
|
-
|
|
64
39
|
export function loadProviderKeys(id, { file = defaultStateFile() } = {}) {
|
|
65
|
-
const env = (process.env[
|
|
40
|
+
const env = (process.env[_providerKeyEnv(id)] || "").trim();
|
|
66
41
|
if (env) return [env];
|
|
67
42
|
const configs = readState(file).providerConfigs;
|
|
68
43
|
const cfg = configs && typeof configs === "object" ? configs[id] : undefined;
|
|
@@ -76,118 +51,97 @@ export function loadProviderKeys(id, { file = defaultStateFile() } = {}) {
|
|
|
76
51
|
if (Array.isArray(v)) return [...new Set(v.filter((x) => typeof x === "string" && x.trim().length))];
|
|
77
52
|
return [];
|
|
78
53
|
}
|
|
79
|
-
|
|
80
|
-
export function loadProviderKey(id, opts = {}) {
|
|
81
|
-
return loadProviderKeys(id, opts)[0] || "";
|
|
82
|
-
}
|
|
83
|
-
|
|
54
|
+
export function loadProviderKey(id, opts = {}) { return loadProviderKeys(id, opts)[0] || ""; }
|
|
84
55
|
export function saveProviderKeys(id, list, { file = defaultStateFile() } = {}) {
|
|
85
56
|
const keys = { ...(readState(file).providerKeys || {}) };
|
|
86
57
|
const clean = [...new Set((Array.isArray(list) ? list : []).map((k) => String(k || "").trim()).filter(Boolean))];
|
|
87
|
-
if (clean.length) keys[id] = clean;
|
|
88
|
-
else delete keys[id];
|
|
58
|
+
if (clean.length) keys[id] = clean; else delete keys[id];
|
|
89
59
|
writeStateImmediate(file, { providerKeys: keys });
|
|
90
60
|
return clean;
|
|
91
61
|
}
|
|
92
|
-
|
|
93
62
|
export function saveProviderKey(id, key, opts = {}) {
|
|
94
63
|
const list = [...loadProviderKeys(id, opts)];
|
|
95
64
|
const clean = String(key || "").trim();
|
|
96
65
|
if (clean && !list.includes(clean)) list.push(clean);
|
|
97
66
|
return saveProviderKeys(id, clean ? list : [], opts);
|
|
98
67
|
}
|
|
99
|
-
|
|
100
|
-
export function
|
|
101
|
-
return saveProviderKey(id, key, opts);
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
export function removeProviderKey(id, key, opts = {}) {
|
|
105
|
-
return removeProviderKeys(id, [key], opts);
|
|
106
|
-
}
|
|
107
|
-
|
|
68
|
+
export function addProviderKey(id, key, opts = {}) { return saveProviderKey(id, key, opts); }
|
|
69
|
+
export function removeProviderKey(id, key, opts = {}) { return removeProviderKeys(id, [key], opts); }
|
|
108
70
|
export function removeProviderKeys(id, targets = [], opts = {}) {
|
|
109
71
|
const set = new Set((Array.isArray(targets) ? targets : [targets]).map((k) => String(k || "").trim()).filter(Boolean));
|
|
110
72
|
const list = loadProviderKeys(id, opts).filter((k) => !set.has(k));
|
|
111
73
|
return saveProviderKeys(id, list, opts);
|
|
112
74
|
}
|
|
113
|
-
|
|
114
75
|
export function loadProviderShareKeys(id, { file = defaultStateFile() } = {}) {
|
|
115
|
-
const env = process.env[
|
|
76
|
+
const env = process.env[_providerShareEnv(id)] || "";
|
|
116
77
|
if (env) return ["1", "true", "on", "yes"].includes(String(env).trim().toLowerCase());
|
|
117
78
|
const map = readState(file).providerShareKeys;
|
|
118
79
|
return !!(map && typeof map === "object" && map[id]);
|
|
119
80
|
}
|
|
120
|
-
|
|
121
81
|
export function saveProviderShareKeys(id, on, { file = defaultStateFile() } = {}) {
|
|
122
82
|
const map = { ...(readState(file).providerShareKeys || {}) };
|
|
123
|
-
if (on) map[id] = true;
|
|
124
|
-
else delete map[id];
|
|
83
|
+
if (on) map[id] = true; else delete map[id];
|
|
125
84
|
writeStateImmediate(file, { providerShareKeys: map });
|
|
126
85
|
return !!on;
|
|
127
86
|
}
|
|
128
|
-
|
|
129
87
|
export function loadProviderConfigs({ file = defaultStateFile() } = {}) {
|
|
130
88
|
const v = readState(file).providerConfigs;
|
|
131
89
|
if (v && typeof v === "object" && !Array.isArray(v)) return v;
|
|
132
90
|
return {};
|
|
133
91
|
}
|
|
134
|
-
|
|
135
92
|
export function loadProviderConfig(id, { file = defaultStateFile() } = {}) {
|
|
136
|
-
const envUrl = (process.env[
|
|
93
|
+
const envUrl = (process.env[_providerBaseUrlEnv(id)] || "").trim();
|
|
137
94
|
const envKeys = loadProviderKeys(id, { file });
|
|
138
|
-
if (envUrl || (process.env[
|
|
139
|
-
const baseUrl = envUrl || loadProviderConfigs({ file })[id]?.baseUrl || (String(id).toLowerCase() === "workbuddy" ?
|
|
95
|
+
if (envUrl || (process.env[_providerKeyEnv(id)] || "").trim()) {
|
|
96
|
+
const baseUrl = envUrl || loadProviderConfigs({ file })[id]?.baseUrl || (String(id).toLowerCase() === "workbuddy" ? _WORKBUDDY : "");
|
|
140
97
|
const cfg = loadProviderConfigs({ file })[id];
|
|
141
|
-
const allowedModels = cfg && Array.isArray(cfg.allowedModels) ? [...new Set(cfg.allowedModels.map((m) =>
|
|
142
|
-
const auths = cfg && Array.isArray(cfg.auths) ?
|
|
143
|
-
const modelsPath = cfg && typeof cfg.modelsPath === "string" && cfg.modelsPath.trim() ?
|
|
144
|
-
const chatPath = cfg && typeof cfg.chatPath === "string" && cfg.chatPath.trim() ?
|
|
145
|
-
if (baseUrl || envKeys.length || allowedModels.length || auths.length) return { baseUrl:
|
|
98
|
+
const allowedModels = cfg && Array.isArray(cfg.allowedModels) ? [...new Set(cfg.allowedModels.map((m) => _normalizeAllowedModel(m, id)).filter(Boolean))] : [];
|
|
99
|
+
const auths = cfg && Array.isArray(cfg.auths) ? _normalizeAuths(cfg.auths) : [];
|
|
100
|
+
const modelsPath = cfg && typeof cfg.modelsPath === "string" && cfg.modelsPath.trim() ? _normalizeEndpointPath(cfg.modelsPath) : _defaultModelsPath(id);
|
|
101
|
+
const chatPath = cfg && typeof cfg.chatPath === "string" && cfg.chatPath.trim() ? _normalizeEndpointPath(cfg.chatPath) : _defaultChatPath(id);
|
|
102
|
+
if (baseUrl || envKeys.length || allowedModels.length || auths.length) return { baseUrl: _normalizeBaseUrl(baseUrl), keys: envKeys, auths, allowedModels, modelsPath, chatPath };
|
|
146
103
|
return null;
|
|
147
104
|
}
|
|
148
105
|
const configs = loadProviderConfigs({ file });
|
|
149
106
|
const cfg = configs[id];
|
|
150
107
|
if (cfg && typeof cfg === "object") {
|
|
151
108
|
return {
|
|
152
|
-
baseUrl:
|
|
109
|
+
baseUrl: _normalizeBaseUrl(cfg.baseUrl || (String(id).toLowerCase() === "workbuddy" ? _WORKBUDDY : "")),
|
|
153
110
|
keys: Array.isArray(cfg.keys) ? [...new Set(cfg.keys.filter((x) => typeof x === "string" && x.trim().length))] : [],
|
|
154
|
-
auths:
|
|
155
|
-
allowedModels: Array.isArray(cfg.allowedModels) ? [...new Set(cfg.allowedModels.map((m) =>
|
|
156
|
-
modelsPath: typeof cfg.modelsPath === "string" && cfg.modelsPath.trim() ?
|
|
157
|
-
chatPath: typeof cfg.chatPath === "string" && cfg.chatPath.trim() ?
|
|
111
|
+
auths: _normalizeAuths(cfg.auths),
|
|
112
|
+
allowedModels: Array.isArray(cfg.allowedModels) ? [...new Set(cfg.allowedModels.map((m) => _normalizeAllowedModel(m, id)).filter(Boolean))] : [],
|
|
113
|
+
modelsPath: typeof cfg.modelsPath === "string" && cfg.modelsPath.trim() ? _normalizeEndpointPath(cfg.modelsPath) : _defaultModelsPath(id),
|
|
114
|
+
chatPath: typeof cfg.chatPath === "string" && cfg.chatPath.trim() ? _normalizeEndpointPath(cfg.chatPath) : _defaultChatPath(id),
|
|
158
115
|
};
|
|
159
116
|
}
|
|
160
117
|
const keys = loadProviderKeys(id, { file });
|
|
161
|
-
if (keys.length) return { baseUrl: String(id).toLowerCase() === "workbuddy" ?
|
|
118
|
+
if (keys.length) return { baseUrl: String(id).toLowerCase() === "workbuddy" ? _WORKBUDDY : "", keys, auths: [], allowedModels: [], modelsPath: _defaultModelsPath(id), chatPath: _defaultChatPath(id) };
|
|
162
119
|
return null;
|
|
163
120
|
}
|
|
164
|
-
|
|
165
121
|
export function loadProviderBaseUrl(id, opts = {}) {
|
|
166
|
-
const env = (process.env[
|
|
167
|
-
if (env) return
|
|
122
|
+
const env = (process.env[_providerBaseUrlEnv(id)] || "").trim();
|
|
123
|
+
if (env) return _normalizeBaseUrl(env);
|
|
168
124
|
const cfg = loadProviderConfigs(opts)[id];
|
|
169
|
-
if (cfg && typeof cfg.baseUrl === "string" &&
|
|
170
|
-
if (String(id || "").toLowerCase() === "workbuddy") return
|
|
125
|
+
if (cfg && typeof cfg.baseUrl === "string" && _normalizeBaseUrl(cfg.baseUrl)) return _normalizeBaseUrl(cfg.baseUrl);
|
|
126
|
+
if (String(id || "").toLowerCase() === "workbuddy") return _WORKBUDDY;
|
|
171
127
|
return "";
|
|
172
128
|
}
|
|
173
|
-
|
|
174
129
|
export function loadProviderAuths(id, { file = defaultStateFile() } = {}) {
|
|
175
130
|
const cfg = loadProviderConfigs({ file })[id];
|
|
176
|
-
if (cfg && Array.isArray(cfg.auths)) return
|
|
131
|
+
if (cfg && Array.isArray(cfg.auths)) return _normalizeAuths(cfg.auths);
|
|
177
132
|
return [];
|
|
178
133
|
}
|
|
179
134
|
export function saveProviderAuths(id, list, { file = defaultStateFile() } = {}) {
|
|
180
|
-
const clean =
|
|
135
|
+
const clean = _normalizeAuths(list);
|
|
181
136
|
const configs = { ...loadProviderConfigs({ file }) };
|
|
182
137
|
const cur = configs[id] && typeof configs[id] === "object" ? configs[id] : {};
|
|
183
|
-
const baseUrl =
|
|
138
|
+
const baseUrl = _normalizeBaseUrl(cur.baseUrl || loadProviderBaseUrl(id, { file }) || "");
|
|
184
139
|
const keys = Array.isArray(cur.keys) ? [...new Set(cur.keys.filter((x) => typeof x === "string" && x.trim().length))] : loadProviderKeys(id, { file });
|
|
185
|
-
const allowedModels = Array.isArray(cur.allowedModels) ? [...new Set(cur.allowedModels.map((m) =>
|
|
186
|
-
const modelsPath = typeof cur.modelsPath === "string" ?
|
|
187
|
-
const chatPath = typeof cur.chatPath === "string" ?
|
|
188
|
-
if (!baseUrl && !keys.length && !clean.length && !allowedModels.length && !modelsPath && !chatPath)
|
|
189
|
-
|
|
190
|
-
} else {
|
|
140
|
+
const allowedModels = Array.isArray(cur.allowedModels) ? [...new Set(cur.allowedModels.map((m) => _normalizeAllowedModel(m, id)).filter(Boolean))] : [];
|
|
141
|
+
const modelsPath = typeof cur.modelsPath === "string" ? _normalizeEndpointPath(cur.modelsPath) : "";
|
|
142
|
+
const chatPath = typeof cur.chatPath === "string" ? _normalizeEndpointPath(cur.chatPath) : "";
|
|
143
|
+
if (!baseUrl && !keys.length && !clean.length && !allowedModels.length && !modelsPath && !chatPath) delete configs[id];
|
|
144
|
+
else {
|
|
191
145
|
configs[id] = { baseUrl, keys };
|
|
192
146
|
if (clean.length) configs[id].auths = clean;
|
|
193
147
|
if (allowedModels.length) configs[id].allowedModels = allowedModels;
|
|
@@ -197,19 +151,17 @@ export function saveProviderAuths(id, list, { file = defaultStateFile() } = {})
|
|
|
197
151
|
writeStateImmediate(file, { providerConfigs: configs });
|
|
198
152
|
return clean;
|
|
199
153
|
}
|
|
200
|
-
|
|
201
154
|
export function saveProviderBaseUrl(id, baseUrl, { file = defaultStateFile() } = {}) {
|
|
202
|
-
const clean =
|
|
155
|
+
const clean = _normalizeBaseUrl(baseUrl);
|
|
203
156
|
const configs = { ...loadProviderConfigs({ file }) };
|
|
204
157
|
const cur = configs[id] && typeof configs[id] === "object" ? configs[id] : {};
|
|
205
158
|
const keys = Array.isArray(cur.keys) ? cur.keys : loadProviderKeys(id, { file });
|
|
206
|
-
const auths =
|
|
207
|
-
const allowedModels = Array.isArray(cur.allowedModels) ? [...new Set(cur.allowedModels.map((m) =>
|
|
208
|
-
const modelsPath = typeof cur.modelsPath === "string" ?
|
|
209
|
-
const chatPath = typeof cur.chatPath === "string" ?
|
|
210
|
-
if (!clean && !keys.length && !auths.length && !allowedModels.length && !modelsPath && !chatPath)
|
|
211
|
-
|
|
212
|
-
} else {
|
|
159
|
+
const auths = _normalizeAuths(cur.auths);
|
|
160
|
+
const allowedModels = Array.isArray(cur.allowedModels) ? [...new Set(cur.allowedModels.map((m) => _normalizeAllowedModel(m, id)).filter(Boolean))] : [];
|
|
161
|
+
const modelsPath = typeof cur.modelsPath === "string" ? _normalizeEndpointPath(cur.modelsPath) : "";
|
|
162
|
+
const chatPath = typeof cur.chatPath === "string" ? _normalizeEndpointPath(cur.chatPath) : "";
|
|
163
|
+
if (!clean && !keys.length && !auths.length && !allowedModels.length && !modelsPath && !chatPath) delete configs[id];
|
|
164
|
+
else {
|
|
213
165
|
configs[id] = { baseUrl: clean, keys };
|
|
214
166
|
if (auths.length) configs[id].auths = auths;
|
|
215
167
|
if (allowedModels.length) configs[id].allowedModels = allowedModels;
|
|
@@ -219,23 +171,21 @@ export function saveProviderBaseUrl(id, baseUrl, { file = defaultStateFile() } =
|
|
|
219
171
|
writeStateImmediate(file, { providerConfigs: configs });
|
|
220
172
|
return clean;
|
|
221
173
|
}
|
|
222
|
-
|
|
223
174
|
export function saveProviderConfig(id, { baseUrl, keys, auths, allowedModels, modelsPath, chatPath }, { file = defaultStateFile() } = {}) {
|
|
224
|
-
const cleanUrl =
|
|
175
|
+
const cleanUrl = _normalizeBaseUrl(baseUrl);
|
|
225
176
|
const cleanKeys = [...new Set((Array.isArray(keys) ? keys : []).map((k) => String(k || "").trim()).filter(Boolean))];
|
|
226
|
-
const cleanAuths = auths === undefined ? undefined :
|
|
227
|
-
const cleanAllowed = [...new Set((Array.isArray(allowedModels) ? allowedModels : []).map((m) =>
|
|
228
|
-
const cleanModelsPath = modelsPath === undefined ? undefined : (String(modelsPath).trim() ?
|
|
229
|
-
const cleanChatPath = chatPath === undefined ? undefined : (String(chatPath).trim() ?
|
|
177
|
+
const cleanAuths = auths === undefined ? undefined : _normalizeAuths(auths);
|
|
178
|
+
const cleanAllowed = [...new Set((Array.isArray(allowedModels) ? allowedModels : []).map((m) => _normalizeAllowedModel(m, id)).filter(Boolean))];
|
|
179
|
+
const cleanModelsPath = modelsPath === undefined ? undefined : (String(modelsPath).trim() ? _normalizeEndpointPath(modelsPath) : "");
|
|
180
|
+
const cleanChatPath = chatPath === undefined ? undefined : (String(chatPath).trim() ? _normalizeEndpointPath(chatPath) : "");
|
|
230
181
|
const configs = { ...loadProviderConfigs({ file }) };
|
|
231
182
|
const cur = configs[id] && typeof configs[id] === "object" ? configs[id] : {};
|
|
232
|
-
const finalAllowed = allowedModels === undefined ? (Array.isArray(cur.allowedModels) ? [...new Set(cur.allowedModels.map((m) =>
|
|
233
|
-
const finalAuths = cleanAuths === undefined ?
|
|
234
|
-
const finalModelsPath = cleanModelsPath === undefined ? (typeof cur.modelsPath === "string" ?
|
|
235
|
-
const finalChatPath = cleanChatPath === undefined ? (typeof cur.chatPath === "string" ?
|
|
236
|
-
if (!cleanUrl && !cleanKeys.length && !finalAllowed.length && !finalAuths.length && !finalModelsPath && !finalChatPath)
|
|
237
|
-
|
|
238
|
-
} else {
|
|
183
|
+
const finalAllowed = allowedModels === undefined ? (Array.isArray(cur.allowedModels) ? [...new Set(cur.allowedModels.map((m) => _normalizeAllowedModel(m, id)).filter(Boolean))] : []) : cleanAllowed;
|
|
184
|
+
const finalAuths = cleanAuths === undefined ? _normalizeAuths(cur.auths) : cleanAuths;
|
|
185
|
+
const finalModelsPath = cleanModelsPath === undefined ? (typeof cur.modelsPath === "string" ? _normalizeEndpointPath(cur.modelsPath) : "") : cleanModelsPath;
|
|
186
|
+
const finalChatPath = cleanChatPath === undefined ? (typeof cur.chatPath === "string" ? _normalizeEndpointPath(cur.chatPath) : "") : cleanChatPath;
|
|
187
|
+
if (!cleanUrl && !cleanKeys.length && !finalAllowed.length && !finalAuths.length && !finalModelsPath && !finalChatPath) delete configs[id];
|
|
188
|
+
else {
|
|
239
189
|
configs[id] = { baseUrl: cleanUrl, keys: cleanKeys };
|
|
240
190
|
if (finalAuths.length) configs[id].auths = finalAuths;
|
|
241
191
|
if (finalAllowed.length) configs[id].allowedModels = finalAllowed;
|
package/src/state/store.js
CHANGED
|
@@ -4,6 +4,7 @@ import { writeFile } from "node:fs/promises";
|
|
|
4
4
|
import { dirname, join } from "node:path";
|
|
5
5
|
import os from "node:os";
|
|
6
6
|
import { loadFromDisk, atomicWriteSync, getMtimeMs, ensureDir } from "./persist.js";
|
|
7
|
+
import { mergeState, COLD_WINS } from "./merge.js";
|
|
7
8
|
|
|
8
9
|
export const DEFAULT_PORT = 8989;
|
|
9
10
|
|
|
@@ -51,34 +52,7 @@ function getEntry(file) {
|
|
|
51
52
|
return e;
|
|
52
53
|
}
|
|
53
54
|
|
|
54
|
-
//
|
|
55
|
-
const COLD_WINS = new Set([
|
|
56
|
-
"providerConfigs",
|
|
57
|
-
"providerKeys",
|
|
58
|
-
"providerShareKeys",
|
|
59
|
-
"port",
|
|
60
|
-
"token",
|
|
61
|
-
"preferredModel",
|
|
62
|
-
"modelPicks",
|
|
63
|
-
"peers",
|
|
64
|
-
"groups",
|
|
65
|
-
"groupsJoined",
|
|
66
|
-
"bans",
|
|
67
|
-
"createdAt",
|
|
68
|
-
]);
|
|
69
|
-
|
|
70
|
-
function mergeState(disk, mem) {
|
|
71
|
-
const merged = { ...disk, ...mem };
|
|
72
|
-
for (const k of COLD_WINS) {
|
|
73
|
-
if (disk[k] !== undefined) merged[k] = disk[k];
|
|
74
|
-
else if (!(k in disk) && k in mem) {
|
|
75
|
-
// deletion on disk: respect deletion by removing from merged if disk intentionally lacks key?
|
|
76
|
-
// Preserve original behavior: keep mem's stale value (do not delete).
|
|
77
|
-
// Uncomment to support true deletion: delete merged[k];
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
return merged;
|
|
81
|
-
}
|
|
55
|
+
// 合并规则已抽至 merge.js 单一真相
|
|
82
56
|
|
|
83
57
|
function readState(file) {
|
|
84
58
|
const e = getEntry(file);
|