mslxdff 0.1.65 → 0.1.66
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/bin/mslxdff.js +3 -3159
- package/package.json +1 -1
- package/src/cli/bootstrap.js +408 -0
- package/src/cli/commands/daemon.js +151 -0
- package/src/cli/commands/group.js +272 -0
- package/src/cli/commands/model.js +405 -0
- package/src/cli/commands/provider/add.js +106 -0
- package/src/cli/commands/provider/allowlist.js +99 -0
- package/src/cli/commands/provider/config.js +82 -0
- package/src/cli/commands/provider/index.js +193 -0
- package/src/cli/commands/provider/keys.js +135 -0
- package/src/cli/commands/provider/models.js +99 -0
- package/src/cli/commands/provider.js +1 -0
- package/src/cli/commands/sync.js +143 -0
- package/src/cli/commands/system.js +236 -0
- package/src/cli/commands/workbuddy.js +91 -0
- package/src/cli/format.js +119 -0
- package/src/cli/group-helpers.js +69 -0
- package/src/cli/help.js +66 -0
- package/src/cli/index.js +59 -0
- package/src/cli/interactive.js +84 -0
- package/src/cli/policy.js +119 -0
- package/src/cli/status.js +332 -0
- package/src/cli/util.js +24 -0
- package/src/providers/base.js +159 -0
- package/src/providers/generic.js +26 -166
- package/src/providers/openrouter.js +32 -162
- package/src/providers/workbuddy.js +7 -32
- package/src/routes/chat/gateway.js +301 -0
- package/src/routes/chat/index.js +1 -384
- package/src/state/facade.js +57 -0
- package/src/state/memory.js +180 -0
- package/src/state/persist.js +42 -0
- package/src/state/schemas/allowlist.js +87 -0
- package/src/state/schemas/group.js +31 -0
- package/src/state/schemas/model.js +53 -0
- package/src/state/schemas/peer.js +31 -0
- package/src/state/schemas/port.js +10 -0
- package/src/state/schemas/provider.js +254 -0
- package/src/state/schemas/token.js +43 -0
- package/src/state/store.js +202 -0
- package/src/state.js +1 -711
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { defaultStateFile, readState, writeStateImmediate } from "../store.js";
|
|
2
|
+
import {
|
|
3
|
+
normalizeAllowedModel,
|
|
4
|
+
normalizeBaseUrl,
|
|
5
|
+
normalizeAuths,
|
|
6
|
+
normalizeEndpointPath,
|
|
7
|
+
loadProviderConfigs,
|
|
8
|
+
loadProviderConfig,
|
|
9
|
+
loadProviderBaseUrl,
|
|
10
|
+
loadProviderKeys,
|
|
11
|
+
} from "./provider.js";
|
|
12
|
+
|
|
13
|
+
export function loadProviderAllowedModels(id, { file = defaultStateFile() } = {}) {
|
|
14
|
+
const cfg = loadProviderConfig(id, { file });
|
|
15
|
+
if (cfg && Array.isArray(cfg.allowedModels)) return [...cfg.allowedModels];
|
|
16
|
+
const raw = readState(file).providerConfigs?.[id];
|
|
17
|
+
if (raw && Array.isArray(raw.allowedModels)) return [...new Set(raw.allowedModels.map((m) => normalizeAllowedModel(m, id)).filter(Boolean))];
|
|
18
|
+
return [];
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function saveProviderAllowedModels(id, list, { file = defaultStateFile() } = {}) {
|
|
22
|
+
const clean = [...new Set((Array.isArray(list) ? list : []).map((m) => normalizeAllowedModel(m, id)).filter(Boolean))];
|
|
23
|
+
const configs = { ...loadProviderConfigs({ file }) };
|
|
24
|
+
const cur = configs[id] && typeof configs[id] === "object" ? configs[id] : {};
|
|
25
|
+
const baseUrl = normalizeBaseUrl(cur.baseUrl || loadProviderBaseUrl(id, { file }) || "");
|
|
26
|
+
const keys = Array.isArray(cur.keys) ? cur.keys : loadProviderKeys(id, { file });
|
|
27
|
+
const auths = normalizeAuths(cur.auths);
|
|
28
|
+
const modelsPath = typeof cur.modelsPath === "string" ? normalizeEndpointPath(cur.modelsPath) : "";
|
|
29
|
+
const chatPath = typeof cur.chatPath === "string" ? normalizeEndpointPath(cur.chatPath) : "";
|
|
30
|
+
if (!baseUrl && !keys.length && !auths.length && !clean.length && !modelsPath && !chatPath) {
|
|
31
|
+
delete configs[id];
|
|
32
|
+
} else {
|
|
33
|
+
configs[id] = { baseUrl, keys };
|
|
34
|
+
if (auths.length) configs[id].auths = auths;
|
|
35
|
+
if (clean.length) configs[id].allowedModels = clean;
|
|
36
|
+
if (modelsPath) configs[id].modelsPath = modelsPath;
|
|
37
|
+
if (chatPath) configs[id].chatPath = chatPath;
|
|
38
|
+
}
|
|
39
|
+
writeStateImmediate(file, { providerConfigs: configs });
|
|
40
|
+
return clean;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function addProviderAllowedModel(id, model, opts = {}) {
|
|
44
|
+
const cur = loadProviderAllowedModels(id, opts);
|
|
45
|
+
const norm = normalizeAllowedModel(model, id);
|
|
46
|
+
if (!norm || cur.includes(norm)) return cur;
|
|
47
|
+
return saveProviderAllowedModels(id, [...cur, norm], opts);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function removeProviderAllowedModel(id, model, opts = {}) {
|
|
51
|
+
return removeProviderAllowedModels(id, [model], opts);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function removeProviderAllowedModels(id, targets = [], opts = {}) {
|
|
55
|
+
const set = new Set((Array.isArray(targets) ? targets : [targets]).map((m) => normalizeAllowedModel(m, id)).filter(Boolean));
|
|
56
|
+
const cur = loadProviderAllowedModels(id, opts);
|
|
57
|
+
const next = cur.filter((m) => !set.has(m));
|
|
58
|
+
return saveProviderAllowedModels(id, next, opts);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function normalizeAllowAny(v, id) {
|
|
62
|
+
if (typeof v === "boolean") return v;
|
|
63
|
+
if (String(id || "").toLowerCase() === "opencode") return true;
|
|
64
|
+
return false;
|
|
65
|
+
}
|
|
66
|
+
export function loadProviderAllowAnyModels(id, { file = defaultStateFile() } = {}) {
|
|
67
|
+
const cfg = readState(file).providerConfigs?.[id];
|
|
68
|
+
if (cfg && typeof cfg.allowAnyModels === "boolean") return cfg.allowAnyModels;
|
|
69
|
+
return normalizeAllowAny(undefined, id);
|
|
70
|
+
}
|
|
71
|
+
export function saveProviderAllowAnyModels(id, allowAny, { file = defaultStateFile() } = {}) {
|
|
72
|
+
const state = readState(file);
|
|
73
|
+
const cfgs = { ...(state.providerConfigs || {}) };
|
|
74
|
+
const cur = cfgs[id] || {};
|
|
75
|
+
cfgs[id] = { ...cur, allowAnyModels: normalizeAllowAny(allowAny, id) };
|
|
76
|
+
writeStateImmediate(file, { providerConfigs: cfgs });
|
|
77
|
+
return cfgs[id].allowAnyModels;
|
|
78
|
+
}
|
|
79
|
+
export function isModelAllowed(id, rawModel, { file = defaultStateFile() } = {}) {
|
|
80
|
+
const raw = String(rawModel || "").trim();
|
|
81
|
+
if (!raw) return true;
|
|
82
|
+
const allowed = loadProviderAllowedModels(id, { file });
|
|
83
|
+
const allowAny = loadProviderAllowAnyModels(id, { file });
|
|
84
|
+
if (!allowed.length) return allowAny;
|
|
85
|
+
const norm = normalizeAllowedModel(raw, id);
|
|
86
|
+
return allowed.includes(norm);
|
|
87
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { defaultStateFile, readState, writeStateImmediate } from "../store.js";
|
|
2
|
+
|
|
3
|
+
export function loadGroups({ file = defaultStateFile() } = {}) {
|
|
4
|
+
const groups = readState(file).groups;
|
|
5
|
+
return groups && typeof groups === "object" && !Array.isArray(groups) ? groups : {};
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export function loadGroupsJoined({ file = defaultStateFile() } = {}) {
|
|
9
|
+
const joined = readState(file).groupsJoined;
|
|
10
|
+
return Array.isArray(joined) ? joined : [];
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function saveGroupsJoined(joined, { file = defaultStateFile() } = {}) {
|
|
14
|
+
writeStateImmediate(file, { groupsJoined: joined });
|
|
15
|
+
return joined;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function loadBans({ file = defaultStateFile() } = {}) {
|
|
19
|
+
const bans = readState(file).bans;
|
|
20
|
+
return bans && typeof bans === "object" && !Array.isArray(bans) ? bans : {};
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function saveBans(bans, { file = defaultStateFile() } = {}) {
|
|
24
|
+
writeStateImmediate(file, { bans });
|
|
25
|
+
return bans;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function saveGroups(groups, { file = defaultStateFile() } = {}) {
|
|
29
|
+
writeStateImmediate(file, { groups });
|
|
30
|
+
return groups;
|
|
31
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { defaultStateFile, readState, writeStateImmediate, writeStateDeferred } from "../store.js";
|
|
2
|
+
|
|
3
|
+
export function loadModelErrors({ file = defaultStateFile() } = {}) {
|
|
4
|
+
const errors = readState(file).modelErrors;
|
|
5
|
+
return errors && typeof errors === "object" && !Array.isArray(errors) ? errors : {};
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export function saveModelErrors(errors, { file = defaultStateFile() } = {}) {
|
|
9
|
+
writeStateDeferred(file, { modelErrors: errors });
|
|
10
|
+
return errors;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function loadModelLatencies({ file = defaultStateFile() } = {}) {
|
|
14
|
+
const lat = readState(file).modelLatencies;
|
|
15
|
+
return lat && typeof lat === "object" && !Array.isArray(lat) ? lat : {};
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function saveModelLatencies(latencies, { file = defaultStateFile() } = {}) {
|
|
19
|
+
writeStateDeferred(file, { modelLatencies: latencies });
|
|
20
|
+
return latencies;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function loadModelStats({ file = defaultStateFile() } = {}) {
|
|
24
|
+
const s = readState(file).modelStats;
|
|
25
|
+
return s && typeof s === "object" && !Array.isArray(s) ? s : {};
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function saveModelStats(stats, { file = defaultStateFile() } = {}) {
|
|
29
|
+
writeStateDeferred(file, { modelStats: stats });
|
|
30
|
+
return stats;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function loadPreferredModel({ file = defaultStateFile() } = {}) {
|
|
34
|
+
const v = readState(file).preferredModel;
|
|
35
|
+
return typeof v === "string" && v.trim() ? v.trim() : null;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function loadModelPicks({ file = defaultStateFile() } = {}) {
|
|
39
|
+
const picks = readState(file).modelPicks;
|
|
40
|
+
if (!Array.isArray(picks)) return [];
|
|
41
|
+
return [...new Set(picks.filter((x) => typeof x === "string" && x.trim().length))];
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function saveModelPicks(picks, { file = defaultStateFile() } = {}) {
|
|
45
|
+
const list = [...new Set((Array.isArray(picks) ? picks : []).filter((x) => typeof x === "string" && x.trim().length))];
|
|
46
|
+
writeStateImmediate(file, { modelPicks: list });
|
|
47
|
+
return list;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function savePreferredModel(id, { file = defaultStateFile() } = {}) {
|
|
51
|
+
writeStateImmediate(file, { preferredModel: String(id || "").trim() });
|
|
52
|
+
return String(id || "").trim();
|
|
53
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { defaultStateFile, readState, writeStateImmediate, writeStateDeferred } from "../store.js";
|
|
2
|
+
|
|
3
|
+
export function loadPeers({ file = defaultStateFile() } = {}) {
|
|
4
|
+
const peers = readState(file).peers;
|
|
5
|
+
return Array.isArray(peers) ? peers : [];
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export function savePeers(peers, { file = defaultStateFile() } = {}) {
|
|
9
|
+
writeStateImmediate(file, { peers });
|
|
10
|
+
return peers;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function loadPeerErrors({ file = defaultStateFile() } = {}) {
|
|
14
|
+
const errors = readState(file).peerErrors;
|
|
15
|
+
return errors && typeof errors === "object" && !Array.isArray(errors) ? errors : {};
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function savePeerErrors(errors, { file = defaultStateFile() } = {}) {
|
|
19
|
+
writeStateDeferred(file, { peerErrors: errors });
|
|
20
|
+
return errors;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function loadPeerStats({ file = defaultStateFile() } = {}) {
|
|
24
|
+
const stats = readState(file).peerStats;
|
|
25
|
+
return stats && typeof stats === "object" && !Array.isArray(stats) ? stats : {};
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function savePeerStats(stats, { file = defaultStateFile() } = {}) {
|
|
29
|
+
writeStateDeferred(file, { peerStats: stats });
|
|
30
|
+
return stats;
|
|
31
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { defaultStateFile, readState, writeStateImmediate } from "../store.js";
|
|
2
|
+
|
|
3
|
+
export function setPort(port, { file = defaultStateFile() } = {}) {
|
|
4
|
+
writeStateImmediate(file, { port: Number(port) });
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export function getPort({ file = defaultStateFile() } = {}) {
|
|
8
|
+
const port = readState(file).port;
|
|
9
|
+
return typeof port === "number" && Number.isInteger(port) && port > 0 ? port : null;
|
|
10
|
+
}
|
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
import { defaultStateFile, readState, writeStateImmediate } from "../store.js";
|
|
2
|
+
|
|
3
|
+
export const WORKBUDDY_DEFAULT_BASE_URL = "https://copilot.tencent.com";
|
|
4
|
+
|
|
5
|
+
export function normalizeBaseUrl(url) {
|
|
6
|
+
const s = String(url || "").trim();
|
|
7
|
+
if (!s) return "";
|
|
8
|
+
return s.replace(/\/+$/, "");
|
|
9
|
+
}
|
|
10
|
+
export function normalizeAuths(list) {
|
|
11
|
+
if (!Array.isArray(list)) return [];
|
|
12
|
+
const out = [];
|
|
13
|
+
for (const a of list) {
|
|
14
|
+
if (!a || typeof a !== "object") continue;
|
|
15
|
+
const uid = String(a.uid || a.userId || "").trim();
|
|
16
|
+
if (!uid) continue;
|
|
17
|
+
out.push({
|
|
18
|
+
uid,
|
|
19
|
+
domain: String(a.domain || "www.codebuddy.cn").trim() || "www.codebuddy.cn",
|
|
20
|
+
enterpriseId: String(a.enterpriseId || a.enterprise_id || "").trim(),
|
|
21
|
+
refreshToken: String(a.refreshToken || a.refresh_token || "").trim(),
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
return out;
|
|
25
|
+
}
|
|
26
|
+
export function normalizeAllowedModel(model, providerId) {
|
|
27
|
+
let s = String(model || "").trim();
|
|
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
|
+
}
|
|
49
|
+
export function loadProviderModelsPath(id, { file = defaultStateFile() } = {}) {
|
|
50
|
+
const cfg = loadProviderConfigs({ file })[id];
|
|
51
|
+
if (cfg && typeof cfg.modelsPath === "string" && cfg.modelsPath.trim()) return normalizeEndpointPath(cfg.modelsPath);
|
|
52
|
+
return defaultModelsPath(id);
|
|
53
|
+
}
|
|
54
|
+
export function loadProviderChatPath(id, { file = defaultStateFile() } = {}) {
|
|
55
|
+
const cfg = loadProviderConfigs({ file })[id];
|
|
56
|
+
if (cfg && typeof cfg.chatPath === "string" && cfg.chatPath.trim()) return normalizeEndpointPath(cfg.chatPath);
|
|
57
|
+
return defaultChatPath(id);
|
|
58
|
+
}
|
|
59
|
+
|
|
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
|
+
export function loadProviderKeys(id, { file = defaultStateFile() } = {}) {
|
|
65
|
+
const env = (process.env[providerKeyEnv(id)] || "").trim();
|
|
66
|
+
if (env) return [env];
|
|
67
|
+
const configs = readState(file).providerConfigs;
|
|
68
|
+
const cfg = configs && typeof configs === "object" ? configs[id] : undefined;
|
|
69
|
+
if (cfg && typeof cfg === "object" && Array.isArray(cfg.keys)) {
|
|
70
|
+
const list = [...new Set(cfg.keys.filter((x) => typeof x === "string" && x.trim().length))];
|
|
71
|
+
if (list.length) return list;
|
|
72
|
+
}
|
|
73
|
+
const keys = readState(file).providerKeys;
|
|
74
|
+
const v = keys && typeof keys === "object" ? keys[id] : undefined;
|
|
75
|
+
if (typeof v === "string") return v.trim() ? [v.trim()] : [];
|
|
76
|
+
if (Array.isArray(v)) return [...new Set(v.filter((x) => typeof x === "string" && x.trim().length))];
|
|
77
|
+
return [];
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export function loadProviderKey(id, opts = {}) {
|
|
81
|
+
return loadProviderKeys(id, opts)[0] || "";
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export function saveProviderKeys(id, list, { file = defaultStateFile() } = {}) {
|
|
85
|
+
const keys = { ...(readState(file).providerKeys || {}) };
|
|
86
|
+
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];
|
|
89
|
+
writeStateImmediate(file, { providerKeys: keys });
|
|
90
|
+
return clean;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export function saveProviderKey(id, key, opts = {}) {
|
|
94
|
+
const list = [...loadProviderKeys(id, opts)];
|
|
95
|
+
const clean = String(key || "").trim();
|
|
96
|
+
if (clean && !list.includes(clean)) list.push(clean);
|
|
97
|
+
return saveProviderKeys(id, clean ? list : [], opts);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export function addProviderKey(id, key, opts = {}) {
|
|
101
|
+
return saveProviderKey(id, key, opts);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export function removeProviderKey(id, key, opts = {}) {
|
|
105
|
+
return removeProviderKeys(id, [key], opts);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export function removeProviderKeys(id, targets = [], opts = {}) {
|
|
109
|
+
const set = new Set((Array.isArray(targets) ? targets : [targets]).map((k) => String(k || "").trim()).filter(Boolean));
|
|
110
|
+
const list = loadProviderKeys(id, opts).filter((k) => !set.has(k));
|
|
111
|
+
return saveProviderKeys(id, list, opts);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export function loadProviderShareKeys(id, { file = defaultStateFile() } = {}) {
|
|
115
|
+
const env = process.env[providerShareEnv(id)] || "";
|
|
116
|
+
if (env) return ["1", "true", "on", "yes"].includes(String(env).trim().toLowerCase());
|
|
117
|
+
const map = readState(file).providerShareKeys;
|
|
118
|
+
return !!(map && typeof map === "object" && map[id]);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export function saveProviderShareKeys(id, on, { file = defaultStateFile() } = {}) {
|
|
122
|
+
const map = { ...(readState(file).providerShareKeys || {}) };
|
|
123
|
+
if (on) map[id] = true;
|
|
124
|
+
else delete map[id];
|
|
125
|
+
writeStateImmediate(file, { providerShareKeys: map });
|
|
126
|
+
return !!on;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export function loadProviderConfigs({ file = defaultStateFile() } = {}) {
|
|
130
|
+
const v = readState(file).providerConfigs;
|
|
131
|
+
if (v && typeof v === "object" && !Array.isArray(v)) return v;
|
|
132
|
+
return {};
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
export function loadProviderConfig(id, { file = defaultStateFile() } = {}) {
|
|
136
|
+
const envUrl = (process.env[providerBaseUrlEnv(id)] || "").trim();
|
|
137
|
+
const envKeys = loadProviderKeys(id, { file });
|
|
138
|
+
if (envUrl || (process.env[providerKeyEnv(id)] || "").trim()) {
|
|
139
|
+
const baseUrl = envUrl || loadProviderConfigs({ file })[id]?.baseUrl || (String(id).toLowerCase() === "workbuddy" ? WORKBUDDY_DEFAULT_BASE_URL : "");
|
|
140
|
+
const cfg = loadProviderConfigs({ file })[id];
|
|
141
|
+
const allowedModels = cfg && Array.isArray(cfg.allowedModels) ? [...new Set(cfg.allowedModels.map((m) => normalizeAllowedModel(m, id)).filter(Boolean))] : [];
|
|
142
|
+
const auths = cfg && Array.isArray(cfg.auths) ? normalizeAuths(cfg.auths) : [];
|
|
143
|
+
const modelsPath = cfg && typeof cfg.modelsPath === "string" && cfg.modelsPath.trim() ? normalizeEndpointPath(cfg.modelsPath) : defaultModelsPath(id);
|
|
144
|
+
const chatPath = cfg && typeof cfg.chatPath === "string" && cfg.chatPath.trim() ? normalizeEndpointPath(cfg.chatPath) : defaultChatPath(id);
|
|
145
|
+
if (baseUrl || envKeys.length || allowedModels.length || auths.length) return { baseUrl: normalizeBaseUrl(baseUrl), keys: envKeys, auths, allowedModels, modelsPath, chatPath };
|
|
146
|
+
return null;
|
|
147
|
+
}
|
|
148
|
+
const configs = loadProviderConfigs({ file });
|
|
149
|
+
const cfg = configs[id];
|
|
150
|
+
if (cfg && typeof cfg === "object") {
|
|
151
|
+
return {
|
|
152
|
+
baseUrl: normalizeBaseUrl(cfg.baseUrl || (String(id).toLowerCase() === "workbuddy" ? WORKBUDDY_DEFAULT_BASE_URL : "")),
|
|
153
|
+
keys: Array.isArray(cfg.keys) ? [...new Set(cfg.keys.filter((x) => typeof x === "string" && x.trim().length))] : [],
|
|
154
|
+
auths: normalizeAuths(cfg.auths),
|
|
155
|
+
allowedModels: Array.isArray(cfg.allowedModels) ? [...new Set(cfg.allowedModels.map((m) => normalizeAllowedModel(m, id)).filter(Boolean))] : [],
|
|
156
|
+
modelsPath: typeof cfg.modelsPath === "string" && cfg.modelsPath.trim() ? normalizeEndpointPath(cfg.modelsPath) : defaultModelsPath(id),
|
|
157
|
+
chatPath: typeof cfg.chatPath === "string" && cfg.chatPath.trim() ? normalizeEndpointPath(cfg.chatPath) : defaultChatPath(id),
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
const keys = loadProviderKeys(id, { file });
|
|
161
|
+
if (keys.length) return { baseUrl: String(id).toLowerCase() === "workbuddy" ? WORKBUDDY_DEFAULT_BASE_URL : "", keys, auths: [], allowedModels: [], modelsPath: defaultModelsPath(id), chatPath: defaultChatPath(id) };
|
|
162
|
+
return null;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
export function loadProviderBaseUrl(id, opts = {}) {
|
|
166
|
+
const env = (process.env[providerBaseUrlEnv(id)] || "").trim();
|
|
167
|
+
if (env) return normalizeBaseUrl(env);
|
|
168
|
+
const cfg = loadProviderConfigs(opts)[id];
|
|
169
|
+
if (cfg && typeof cfg.baseUrl === "string" && normalizeBaseUrl(cfg.baseUrl)) return normalizeBaseUrl(cfg.baseUrl);
|
|
170
|
+
if (String(id || "").toLowerCase() === "workbuddy") return WORKBUDDY_DEFAULT_BASE_URL;
|
|
171
|
+
return "";
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
export function loadProviderAuths(id, { file = defaultStateFile() } = {}) {
|
|
175
|
+
const cfg = loadProviderConfigs({ file })[id];
|
|
176
|
+
if (cfg && Array.isArray(cfg.auths)) return normalizeAuths(cfg.auths);
|
|
177
|
+
return [];
|
|
178
|
+
}
|
|
179
|
+
export function saveProviderAuths(id, list, { file = defaultStateFile() } = {}) {
|
|
180
|
+
const clean = normalizeAuths(list);
|
|
181
|
+
const configs = { ...loadProviderConfigs({ file }) };
|
|
182
|
+
const cur = configs[id] && typeof configs[id] === "object" ? configs[id] : {};
|
|
183
|
+
const baseUrl = normalizeBaseUrl(cur.baseUrl || loadProviderBaseUrl(id, { file }) || "");
|
|
184
|
+
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) => normalizeAllowedModel(m, id)).filter(Boolean))] : [];
|
|
186
|
+
const modelsPath = typeof cur.modelsPath === "string" ? normalizeEndpointPath(cur.modelsPath) : "";
|
|
187
|
+
const chatPath = typeof cur.chatPath === "string" ? normalizeEndpointPath(cur.chatPath) : "";
|
|
188
|
+
if (!baseUrl && !keys.length && !clean.length && !allowedModels.length && !modelsPath && !chatPath) {
|
|
189
|
+
delete configs[id];
|
|
190
|
+
} else {
|
|
191
|
+
configs[id] = { baseUrl, keys };
|
|
192
|
+
if (clean.length) configs[id].auths = clean;
|
|
193
|
+
if (allowedModels.length) configs[id].allowedModels = allowedModels;
|
|
194
|
+
if (modelsPath) configs[id].modelsPath = modelsPath;
|
|
195
|
+
if (chatPath) configs[id].chatPath = chatPath;
|
|
196
|
+
}
|
|
197
|
+
writeStateImmediate(file, { providerConfigs: configs });
|
|
198
|
+
return clean;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
export function saveProviderBaseUrl(id, baseUrl, { file = defaultStateFile() } = {}) {
|
|
202
|
+
const clean = normalizeBaseUrl(baseUrl);
|
|
203
|
+
const configs = { ...loadProviderConfigs({ file }) };
|
|
204
|
+
const cur = configs[id] && typeof configs[id] === "object" ? configs[id] : {};
|
|
205
|
+
const keys = Array.isArray(cur.keys) ? cur.keys : loadProviderKeys(id, { file });
|
|
206
|
+
const auths = normalizeAuths(cur.auths);
|
|
207
|
+
const allowedModels = Array.isArray(cur.allowedModels) ? [...new Set(cur.allowedModels.map((m) => normalizeAllowedModel(m, id)).filter(Boolean))] : [];
|
|
208
|
+
const modelsPath = typeof cur.modelsPath === "string" ? normalizeEndpointPath(cur.modelsPath) : "";
|
|
209
|
+
const chatPath = typeof cur.chatPath === "string" ? normalizeEndpointPath(cur.chatPath) : "";
|
|
210
|
+
if (!clean && !keys.length && !auths.length && !allowedModels.length && !modelsPath && !chatPath) {
|
|
211
|
+
delete configs[id];
|
|
212
|
+
} else {
|
|
213
|
+
configs[id] = { baseUrl: clean, keys };
|
|
214
|
+
if (auths.length) configs[id].auths = auths;
|
|
215
|
+
if (allowedModels.length) configs[id].allowedModels = allowedModels;
|
|
216
|
+
if (modelsPath) configs[id].modelsPath = modelsPath;
|
|
217
|
+
if (chatPath) configs[id].chatPath = chatPath;
|
|
218
|
+
}
|
|
219
|
+
writeStateImmediate(file, { providerConfigs: configs });
|
|
220
|
+
return clean;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
export function saveProviderConfig(id, { baseUrl, keys, auths, allowedModels, modelsPath, chatPath }, { file = defaultStateFile() } = {}) {
|
|
224
|
+
const cleanUrl = normalizeBaseUrl(baseUrl);
|
|
225
|
+
const cleanKeys = [...new Set((Array.isArray(keys) ? keys : []).map((k) => String(k || "").trim()).filter(Boolean))];
|
|
226
|
+
const cleanAuths = auths === undefined ? undefined : normalizeAuths(auths);
|
|
227
|
+
const cleanAllowed = [...new Set((Array.isArray(allowedModels) ? allowedModels : []).map((m) => normalizeAllowedModel(m, id)).filter(Boolean))];
|
|
228
|
+
const cleanModelsPath = modelsPath === undefined ? undefined : (String(modelsPath).trim() ? normalizeEndpointPath(modelsPath) : "");
|
|
229
|
+
const cleanChatPath = chatPath === undefined ? undefined : (String(chatPath).trim() ? normalizeEndpointPath(chatPath) : "");
|
|
230
|
+
const configs = { ...loadProviderConfigs({ file }) };
|
|
231
|
+
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) => normalizeAllowedModel(m, id)).filter(Boolean))] : []) : cleanAllowed;
|
|
233
|
+
const finalAuths = cleanAuths === undefined ? normalizeAuths(cur.auths) : cleanAuths;
|
|
234
|
+
const finalModelsPath = cleanModelsPath === undefined ? (typeof cur.modelsPath === "string" ? normalizeEndpointPath(cur.modelsPath) : "") : cleanModelsPath;
|
|
235
|
+
const finalChatPath = cleanChatPath === undefined ? (typeof cur.chatPath === "string" ? normalizeEndpointPath(cur.chatPath) : "") : cleanChatPath;
|
|
236
|
+
if (!cleanUrl && !cleanKeys.length && !finalAllowed.length && !finalAuths.length && !finalModelsPath && !finalChatPath) {
|
|
237
|
+
delete configs[id];
|
|
238
|
+
} else {
|
|
239
|
+
configs[id] = { baseUrl: cleanUrl, keys: cleanKeys };
|
|
240
|
+
if (finalAuths.length) configs[id].auths = finalAuths;
|
|
241
|
+
if (finalAllowed.length) configs[id].allowedModels = finalAllowed;
|
|
242
|
+
if (finalModelsPath) configs[id].modelsPath = finalModelsPath;
|
|
243
|
+
if (finalChatPath) configs[id].chatPath = finalChatPath;
|
|
244
|
+
}
|
|
245
|
+
const oldKeys = readState(file).providerKeys;
|
|
246
|
+
if (oldKeys && typeof oldKeys === "object" && oldKeys[id] !== undefined) {
|
|
247
|
+
const nextKeys = { ...oldKeys };
|
|
248
|
+
delete nextKeys[id];
|
|
249
|
+
writeStateImmediate(file, { providerKeys: nextKeys, providerConfigs: configs });
|
|
250
|
+
return { baseUrl: cleanUrl, keys: cleanKeys, auths: finalAuths, allowedModels: finalAllowed, modelsPath: finalModelsPath, chatPath: finalChatPath };
|
|
251
|
+
}
|
|
252
|
+
writeStateImmediate(file, { providerConfigs: configs });
|
|
253
|
+
return { baseUrl: cleanUrl, keys: cleanKeys, auths: finalAuths, allowedModels: finalAllowed, modelsPath: finalModelsPath, chatPath: finalChatPath };
|
|
254
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { mkdirSync, readFileSync, writeFileSync, existsSync, statSync } from "node:fs";
|
|
2
|
+
import { dirname } from "node:path";
|
|
3
|
+
import { defaultStateFile, tokenFile, generateToken, readState, writeStateImmediate, getEntry } from "../store.js";
|
|
4
|
+
|
|
5
|
+
function syncTokenFile(token, file) {
|
|
6
|
+
try {
|
|
7
|
+
const tf = tokenFile(file);
|
|
8
|
+
mkdirSync(dirname(tf), { recursive: true });
|
|
9
|
+
writeFileSync(tf, String(token || "").trim() + "\n", "utf8");
|
|
10
|
+
} catch {}
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export async function loadToken({ file = defaultStateFile() } = {}) {
|
|
14
|
+
const state = readState(file);
|
|
15
|
+
if (typeof state.token === "string" && state.token.length > 0) {
|
|
16
|
+
syncTokenFile(state.token, file);
|
|
17
|
+
return { token: state.token, created: false };
|
|
18
|
+
}
|
|
19
|
+
if (existsSync(file)) {
|
|
20
|
+
try {
|
|
21
|
+
const raw = JSON.parse(readFileSync(file, "utf8"));
|
|
22
|
+
if (typeof raw.token === "string" && raw.token.length > 0) {
|
|
23
|
+
syncTokenFile(raw.token, file);
|
|
24
|
+
const e = getEntry(file);
|
|
25
|
+
e.data = raw;
|
|
26
|
+
try { e.mtimeMs = statSync(file).mtimeMs; } catch {}
|
|
27
|
+
return { token: raw.token, created: false };
|
|
28
|
+
}
|
|
29
|
+
} catch {}
|
|
30
|
+
}
|
|
31
|
+
const tok = generateToken();
|
|
32
|
+
const saved = writeStateImmediate(file, { token: tok, createdAt: new Date().toISOString() }).token;
|
|
33
|
+
syncTokenFile(saved, file);
|
|
34
|
+
return { token: saved, created: true };
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export async function refreshToken({ file = defaultStateFile() } = {}) {
|
|
38
|
+
const tok = writeStateImmediate(file, { token: generateToken(), createdAt: new Date().toISOString() }).token;
|
|
39
|
+
syncTokenFile(tok, file);
|
|
40
|
+
return tok;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export { generateToken, tokenFile };
|