mslxdff 0.1.199 → 0.1.203
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 +3 -3
- package/bin/mslxdff.js +1 -1
- package/package.json +38 -38
- package/plugins/README.md +14 -14
- package/plugins/prefer-model.mjs.example +14 -14
- package/src/anthropic/count-tokens.js +61 -61
- package/src/anthropic/request-body.js +126 -126
- package/src/anthropic/shared.js +18 -18
- package/src/anthropic/sse-aggregate.js +56 -56
- package/src/anthropic/translate.js +287 -287
- package/src/anthropic/web-search.js +361 -361
- package/src/api-kind.js +31 -31
- package/src/autostart.js +288 -288
- package/src/bench/probe.js +1 -1
- package/src/bench/runner.js +1 -1
- package/src/bench/via-probe.js +1 -1
- package/src/bench/via.js +144 -144
- package/src/bench/workbuddy-bench.js +1 -1
- package/src/chat/config.js +11 -11
- package/src/chat/cooling.js +81 -81
- package/src/chat/direct.js +58 -58
- package/src/chat/index.js +6 -6
- package/src/chat/spinner.js +38 -38
- package/src/chat/sse.js +69 -69
- package/src/chat/stats.js +247 -247
- package/src/chat/store.js +48 -48
- package/src/chat/tool-handlers.js +67 -67
- package/src/chat/tools.js +1 -1
- package/src/chat/upstream.js +59 -59
- package/src/chat-pipeline/auto-race.js +158 -158
- package/src/chat-pipeline/empty-turn.js +147 -147
- package/src/chat-pipeline/engine.js +17 -17
- package/src/chat-pipeline/serial-trial.js +283 -283
- package/src/chat-pipeline/terminal-verdict.js +147 -147
- package/src/chooser.js +54 -54
- package/src/claude-desktop/apply.js +101 -101
- package/src/claude-desktop/context1m.js +38 -38
- package/src/claude-desktop/doctor.js +146 -146
- package/src/claude-desktop/slots.js +152 -152
- package/src/cli/bootstrap.js +1 -1
- package/src/cli/commands/claude-desktop.js +284 -284
- package/src/cli/commands/daemon.js +231 -231
- package/src/cli/commands/model/list-live.js +22 -22
- package/src/cli/commands/model/list-providers.js +76 -76
- package/src/cli/commands/model/list-render.js +77 -77
- package/src/cli/commands/model/list-sort.js +52 -52
- package/src/cli/commands/model/list-tty.js +76 -76
- package/src/cli/commands/model/list.js +166 -166
- package/src/cli/commands/model/live-models.js +24 -24
- package/src/cli/commands/model/stats.js +43 -43
- package/src/cli/commands/model/status.js +47 -47
- package/src/cli/commands/model.js +51 -51
- package/src/cli/commands/provider/add.js +108 -108
- package/src/cli/commands/provider/allowlist.js +99 -99
- package/src/cli/commands/provider/cline-free.js +99 -99
- package/src/cli/commands/provider/cline-login.js +218 -218
- package/src/cli/commands/provider/cline-quota.js +65 -65
- package/src/cli/commands/provider/codearts-login.js +45 -45
- package/src/cli/commands/provider/config.js +119 -119
- package/src/cli/commands/provider/globalqwenwork-login.js +126 -126
- package/src/cli/commands/provider/keys-guard.js +50 -50
- package/src/cli/commands/provider/keys.js +161 -161
- package/src/cli/commands/provider/qoder-checkin.js +81 -81
- package/src/cli/commands/provider/qoder-login.js +102 -102
- package/src/cli/commands/provider/raccoon-login.js +180 -180
- package/src/cli/commands/provider/raccoon-password.js +162 -162
- package/src/cli/commands/provider/raccoon-quota.js +109 -109
- package/src/cli/commands/provider/traework-checkin.js +144 -144
- package/src/cli/commands/provider/traework-login.js +164 -164
- package/src/cli/commands/provider/workbuddy-login.js +153 -153
- package/src/cli/commands/provider/zcode-login.js +77 -77
- package/src/cli/commands/provider/zcode-quota.js +55 -55
- package/src/cli/commands/provider.js +1 -1
- package/src/cli/commands/stats.js +149 -149
- package/src/cli/commands/system.js +261 -261
- package/src/cli/commands/timezone.js +47 -47
- package/src/cli/commands/use-group.js +60 -60
- package/src/cli/commands/workbuddy.js +189 -189
- package/src/cli/format.js +141 -141
- package/src/cli/group-helpers.js +70 -70
- package/src/cli/help.js +80 -80
- package/src/cli/index.js +70 -70
- package/src/cli/interactive.js +121 -121
- package/src/cli/join-core.js +99 -99
- package/src/cli/join-wizard.js +175 -175
- package/src/cli/policy.js +136 -136
- package/src/cli/provider-row.js +126 -126
- package/src/cli/util.js +24 -24
- package/src/compat.js +53 -53
- package/src/events.js +38 -38
- package/src/free-lane.js +131 -131
- package/src/free-watcher.js +63 -63
- package/src/groups.js +262 -262
- package/src/logs.js +195 -195
- package/src/metrics.js +62 -62
- package/src/model-capabilities/context-lookup.js +65 -65
- package/src/model-capabilities/merge.js +142 -142
- package/src/model-trace.js +187 -187
- package/src/models.js +1 -1
- package/src/opencode-identity.js +72 -72
- package/src/peers.js +254 -251
- package/src/plugins.js +107 -107
- package/src/providers/base.js +164 -164
- package/src/providers/classify.js +22 -22
- package/src/providers/cline/allowlist-sync.js +93 -93
- package/src/providers/cline/free-catalog.js +71 -71
- package/src/providers/cline/headers.js +37 -37
- package/src/providers/cline/index.js +130 -130
- package/src/providers/cline/models.js +150 -150
- package/src/providers/cline/usage.js +274 -274
- package/src/providers/codearts/auth-pool.js +148 -148
- package/src/providers/codearts/concurrency.js +34 -34
- package/src/providers/codearts/const.js +65 -65
- package/src/providers/codearts/dpop.js +95 -95
- package/src/providers/codearts/index.js +175 -175
- package/src/providers/codearts/login.js +214 -214
- package/src/providers/codearts/models.js +182 -182
- package/src/providers/codearts/sign.js +96 -96
- package/src/providers/codearts/sse.js +204 -204
- package/src/providers/codearts/stream.js +209 -209
- package/src/providers/codearts/sts.js +124 -124
- package/src/providers/codearts.js +1 -1
- package/src/providers/dispatcher.js +146 -146
- package/src/providers/generic.js +156 -156
- package/src/providers/globalqwenwork/account-store.js +141 -141
- package/src/providers/globalqwenwork/constants.js +73 -73
- package/src/providers/globalqwenwork/crypto.js +2 -2
- package/src/providers/globalqwenwork/http.js +2 -2
- package/src/providers/globalqwenwork/index.js +331 -331
- package/src/providers/globalqwenwork/payload.js +147 -147
- package/src/providers/globalqwenwork/rsa.js +2 -2
- package/src/providers/globalqwenwork/sse.js +2 -2
- package/src/providers/globalqwenwork/stream.js +2 -2
- package/src/providers/globalqwenwork.js +1 -1
- package/src/providers/globalworkbuddy/account-store.js +198 -198
- package/src/providers/globalworkbuddy/auth.js +97 -97
- package/src/providers/globalworkbuddy/catalog.js +138 -138
- package/src/providers/globalworkbuddy/constants.js +73 -73
- package/src/providers/globalworkbuddy/device-auth.js +120 -120
- package/src/providers/globalworkbuddy/errors.js +98 -98
- package/src/providers/globalworkbuddy/headers.js +89 -89
- package/src/providers/globalworkbuddy/models.js +96 -96
- package/src/providers/globalworkbuddy/payload.js +80 -80
- package/src/providers/index.js +5 -5
- package/src/providers/model-id.js +172 -172
- package/src/providers/opencode.js +19 -19
- package/src/providers/openrouter.js +31 -31
- package/src/providers/qoder/account-store.js +106 -106
- package/src/providers/qoder/aggregate.js +60 -60
- package/src/providers/qoder/baseprompt.json +591 -591
- package/src/providers/qoder/checkin.js +160 -160
- package/src/providers/qoder/constants.js +44 -44
- package/src/providers/qoder/encode.js +41 -41
- package/src/providers/qoder/fingerprint.js +38 -38
- package/src/providers/qoder/models.js +77 -77
- package/src/providers/qoder/oauth.js +84 -84
- package/src/providers/qoder/payload.js +162 -162
- package/src/providers/qoder/request.js +27 -27
- package/src/providers/qoder/session.js +134 -134
- package/src/providers/qoder/sticky.js +38 -38
- package/src/providers/qoder/stream.js +116 -116
- package/src/providers/qoder.js +1 -1
- package/src/providers/raccoon/account-store.js +216 -216
- package/src/providers/raccoon/auth.js +67 -67
- package/src/providers/raccoon/chat.js +228 -228
- package/src/providers/raccoon/const.js +180 -165
- package/src/providers/raccoon/credits.js +185 -175
- package/src/providers/raccoon/crypto.js +43 -0
- package/src/providers/raccoon/envelope.js +31 -31
- package/src/providers/raccoon/headers.js +45 -45
- package/src/providers/raccoon/index.js +216 -216
- package/src/providers/raccoon/login.js +279 -279
- package/src/providers/raccoon/models.js +160 -160
- package/src/providers/raccoon/password-login.js +102 -87
- package/src/providers/raccoon/qr-codewords.js +115 -115
- package/src/providers/raccoon/qr.js +236 -236
- package/src/providers/raccoon/sse.js +135 -135
- package/src/providers/responses-channel.js +120 -120
- package/src/providers/share-keys.js +53 -53
- package/src/providers/traework/account-store.js +234 -234
- package/src/providers/traework/chat.js +217 -217
- package/src/providers/traework/checkin.js +77 -77
- package/src/providers/traework/constants.js +41 -41
- package/src/providers/traework/errors.js +52 -52
- package/src/providers/traework/headers.js +79 -79
- package/src/providers/traework/index.js +132 -132
- package/src/providers/traework/market-identity.js +116 -116
- package/src/providers/traework/models.js +98 -98
- package/src/providers/traework/payload.js +86 -86
- package/src/providers/traework/sse.js +184 -184
- package/src/providers/traework/token.js +52 -52
- package/src/providers/traework.js +2 -2
- package/src/providers/workbuddy/account-store.js +186 -186
- package/src/providers/workbuddy/auth.js +158 -158
- package/src/providers/workbuddy/balance.js +86 -86
- package/src/providers/workbuddy/cat-travel.js +125 -125
- package/src/providers/workbuddy/checkin.js +75 -75
- package/src/providers/workbuddy/desktop-info.js +78 -78
- package/src/providers/workbuddy/growth-api.js +67 -67
- package/src/providers/workbuddy/growth-plans.js +63 -63
- package/src/providers/workbuddy/growth.js +181 -181
- package/src/providers/workbuddy/index.js +217 -217
- package/src/providers/workbuddy/models.js +126 -126
- package/src/providers/workbuddy/payload.js +141 -141
- package/src/providers/workbuddy/rotation-log.js +83 -83
- package/src/providers/workbuddy/sanitize-tools.js +61 -61
- package/src/providers/workbuddy/sdk-chat.js +26 -26
- package/src/providers/workbuddy-balance.js +78 -78
- package/src/providers/zcode/account-store.js +129 -129
- package/src/providers/zcode/auth.js +28 -28
- package/src/providers/zcode/chat.js +179 -179
- package/src/providers/zcode/const.js +54 -54
- package/src/providers/zcode/context-shape.js +162 -162
- package/src/providers/zcode/headers.js +93 -93
- package/src/providers/zcode/index.js +124 -124
- package/src/providers/zcode/models.js +65 -65
- package/src/providers/zcode/oauth.js +120 -120
- package/src/providers/zcode/quota.js +176 -176
- package/src/providers/zcode/sse.js +196 -196
- package/src/readline-compat.js +25 -25
- package/src/responses/translate.js +311 -311
- package/src/routes/chat/broadband-handler.js +72 -72
- package/src/routes/chat/empty-turn-report.js +31 -31
- package/src/routes/chat/exhausted-handler.js +87 -87
- package/src/routes/chat/hedge-handler.js +130 -130
- package/src/routes/chat/index.js +1 -1
- package/src/routes/chat/local-handler.js +63 -63
- package/src/routes/chat/peer-handler.js +81 -81
- package/src/routes/chat.js +3 -3
- package/src/routes/fallback.js +88 -88
- package/src/routes/groups-relay.js +214 -214
- package/src/routes/groups.js +122 -122
- package/src/routes/helpers.js +83 -83
- package/src/routes/index.js +147 -147
- package/src/routes/messages-route.js +365 -365
- package/src/routes/peers.js +23 -1
- package/src/routes/relay-queue.js +181 -181
- package/src/routes/relay.js +60 -60
- package/src/routes/responses-route.js +213 -213
- package/src/routes/stream-body.js +84 -84
- package/src/routes/stream-hold.js +337 -337
- package/src/routes/stream-scan.js +366 -366
- package/src/routes.js +3 -3
- package/src/runtime/auto-update.js +113 -113
- package/src/runtime/bootstrap.js +8 -0
- package/src/runtime/broadband-stream.js +79 -79
- package/src/runtime/broadband.js +103 -103
- package/src/runtime/group-sync.js +81 -81
- package/src/runtime/keep-warm.js +120 -0
- package/src/runtime/lifecycle-forensics.js +116 -116
- package/src/runtime/lifecycle-log.js +87 -87
- package/src/runtime/log-stamp.js +73 -73
- package/src/runtime/proc-info.js +137 -137
- package/src/runtime/qoder-checkin.js +93 -93
- package/src/runtime/raccoon-checkin.js +126 -126
- package/src/runtime/server-lifecycle.js +189 -189
- package/src/runtime/traework-checkin.js +118 -118
- package/src/runtime/workbuddy-checkin.js +120 -120
- package/src/runtime/workbuddy-growth.js +133 -133
- package/src/server.js +7 -0
- package/src/state/facade.js +58 -58
- package/src/state/memory.js +162 -162
- package/src/state/merge.js +25 -25
- package/src/state/migrations/cline-unify.js +94 -94
- package/src/state/migrations.js +33 -33
- package/src/state/persist.js +42 -42
- package/src/state/provider-config.js +142 -142
- package/src/state/schemas/group.js +31 -31
- package/src/state/schemas/model.js +94 -94
- package/src/state/schemas/peer.js +31 -31
- package/src/state/schemas/port.js +10 -10
- package/src/state/schemas/provider.js +202 -202
- package/src/state/schemas/timezone.js +70 -70
- package/src/state/schemas/token.js +48 -48
- package/src/state/schemas/use-group.js +102 -102
- package/src/state/store.js +176 -176
- package/src/state.js +1 -1
- package/src/sync-claude-desktop.js +234 -234
- package/src/sync-claude.js +152 -152
- package/src/sync-codex.js +116 -116
- package/src/sync-workbuddy.js +158 -158
- package/src/talk-full-redact.js +129 -129
- package/src/talk-full.js +131 -131
- package/src/talk-log.js +257 -257
- package/src/text-tokens.js +52 -52
- package/src/thinking-effort.js +67 -67
- package/src/time.js +106 -106
- package/src/timeline.js +26 -26
- package/src/transport/index.js +1 -1
- package/src/upstream-engine/index.js +74 -72
- package/src/upstream-engine/mode.js +15 -15
- package/src/upstream-engine/sdk/attempt.js +221 -221
- package/src/upstream-engine/sdk/chat.js +28 -28
- package/src/upstream-engine/sdk/convert.js +192 -192
- package/src/upstream-engine/sdk/diagnose.js +62 -62
- package/src/upstream-engine/sdk/dispatch.js +55 -55
- package/src/upstream-engine/sdk/responses.js +176 -176
- package/src/upstream-engine/sdk/sse.js +166 -166
- package/src/upstream-responses.js +305 -305
- package/src/upstream.js +53 -1
- package/src/usage/record.js +106 -106
- package/src/usage/report.js +195 -195
- package/src/v6pool/claim.js +157 -157
- package/src/v6pool/config.js +65 -65
- package/src/v6pool/detect.js +130 -130
- package/src/v6pool/index.js +152 -152
- package/src/v6pool/prefix.js +140 -140
- package/src/v6pool/probe.js +40 -40
- package/src/v6pool/report.js +104 -104
- package/src/v6pool/rotate.js +263 -239
- package/src/warp-pool/config.js +74 -74
- package/src/warp-pool/health.js +106 -106
- package/src/warp-pool/identity.js +141 -141
- package/src/warp-pool/index.js +358 -358
- package/src/warp-pool/keys.js +29 -29
- package/src/warp-pool/nodes.js +95 -95
- package/src/warp-pool/ports.js +160 -160
- package/src/warp-pool/register.js +113 -113
- package/src/warp-pool/report.js +232 -232
- package/src/warp-pool/revive.js +168 -168
- package/src/warp-pool/rotate.js +214 -214
- package/src/warp-pool/strategy.js +43 -43
- package/src/warp-pool/verdict.js +69 -69
- package/src/warp-pool/wireproxy.js +162 -162
- package/docs/cli_help_mini.md +0 -146
- package/src/providers/AGENTS.md +0 -49
- package/src/routes/AGENTS.md +0 -37
package/src/state/persist.js
CHANGED
|
@@ -1,42 +1,42 @@
|
|
|
1
|
-
import { mkdirSync, readFileSync, writeFileSync, statSync, renameSync, unlinkSync } from "node:fs";
|
|
2
|
-
import { writeFile } from "node:fs/promises";
|
|
3
|
-
import { dirname } from "node:path";
|
|
4
|
-
|
|
5
|
-
export function loadFromDisk(file) {
|
|
6
|
-
try {
|
|
7
|
-
return JSON.parse(readFileSync(file, "utf8"));
|
|
8
|
-
} catch {
|
|
9
|
-
return {};
|
|
10
|
-
}
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
export function ensureDir(file) {
|
|
14
|
-
mkdirSync(dirname(file), { recursive: true });
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
export function getMtimeMs(file) {
|
|
18
|
-
try {
|
|
19
|
-
return statSync(file).mtimeMs;
|
|
20
|
-
} catch {
|
|
21
|
-
return Date.now();
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
export function atomicWriteSync(file, data) {
|
|
26
|
-
ensureDir(file);
|
|
27
|
-
const tmp = `${file}.tmp.${Date.now()}-${Math.random().toString(36).slice(2, 6)}`;
|
|
28
|
-
writeFileSync(tmp, JSON.stringify(data, null, 2), { mode: 0o600 });
|
|
29
|
-
try {
|
|
30
|
-
renameSync(tmp, file);
|
|
31
|
-
} catch {
|
|
32
|
-
try { writeFileSync(file, JSON.stringify(data, null, 2), { mode: 0o600 }); } catch {}
|
|
33
|
-
try { unlinkSync(tmp); } catch {}
|
|
34
|
-
}
|
|
35
|
-
return getMtimeMs(file);
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
export async function atomicWriteAsync(file, data) {
|
|
39
|
-
ensureDir(file);
|
|
40
|
-
await writeFile(file, JSON.stringify(data, null, 2), { mode: 0o600 });
|
|
41
|
-
return getMtimeMs(file);
|
|
42
|
-
}
|
|
1
|
+
import { mkdirSync, readFileSync, writeFileSync, statSync, renameSync, unlinkSync } from "node:fs";
|
|
2
|
+
import { writeFile } from "node:fs/promises";
|
|
3
|
+
import { dirname } from "node:path";
|
|
4
|
+
|
|
5
|
+
export function loadFromDisk(file) {
|
|
6
|
+
try {
|
|
7
|
+
return JSON.parse(readFileSync(file, "utf8"));
|
|
8
|
+
} catch {
|
|
9
|
+
return {};
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function ensureDir(file) {
|
|
14
|
+
mkdirSync(dirname(file), { recursive: true });
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function getMtimeMs(file) {
|
|
18
|
+
try {
|
|
19
|
+
return statSync(file).mtimeMs;
|
|
20
|
+
} catch {
|
|
21
|
+
return Date.now();
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function atomicWriteSync(file, data) {
|
|
26
|
+
ensureDir(file);
|
|
27
|
+
const tmp = `${file}.tmp.${Date.now()}-${Math.random().toString(36).slice(2, 6)}`;
|
|
28
|
+
writeFileSync(tmp, JSON.stringify(data, null, 2), { mode: 0o600 });
|
|
29
|
+
try {
|
|
30
|
+
renameSync(tmp, file);
|
|
31
|
+
} catch {
|
|
32
|
+
try { writeFileSync(file, JSON.stringify(data, null, 2), { mode: 0o600 }); } catch {}
|
|
33
|
+
try { unlinkSync(tmp); } catch {}
|
|
34
|
+
}
|
|
35
|
+
return getMtimeMs(file);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export async function atomicWriteAsync(file, data) {
|
|
39
|
+
ensureDir(file);
|
|
40
|
+
await writeFile(file, JSON.stringify(data, null, 2), { mode: 0o600 });
|
|
41
|
+
return getMtimeMs(file);
|
|
42
|
+
}
|
|
@@ -1,142 +1,142 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* ProviderConfig 深模块:7 字段 validate/normalize/merge 单点
|
|
3
|
-
* 纯函数,不读 env/file,直接由调用方注入三级数据
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
export const WORKBUDDY_DEFAULT_BASE_URL = "https://copilot.tencent.com";
|
|
7
|
-
|
|
8
|
-
export function normalizeBaseUrl(url) {
|
|
9
|
-
const s = String(url || "").trim();
|
|
10
|
-
if (!s) return "";
|
|
11
|
-
return s.replace(/\/+$/, "");
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
export function normalizeAuths(list) {
|
|
15
|
-
if (!Array.isArray(list)) return [];
|
|
16
|
-
const out = [];
|
|
17
|
-
for (const a of list) {
|
|
18
|
-
if (!a || typeof a !== "object") continue;
|
|
19
|
-
const uid = String(a.uid || a.userId || "").trim();
|
|
20
|
-
if (!uid) continue;
|
|
21
|
-
out.push({
|
|
22
|
-
uid,
|
|
23
|
-
domain: String(a.domain || "www.codebuddy.cn").trim() || "www.codebuddy.cn",
|
|
24
|
-
enterpriseId: String(a.enterpriseId || a.enterprise_id || "").trim(),
|
|
25
|
-
refreshToken: String(a.refreshToken || a.refresh_token || "").trim(),
|
|
26
|
-
});
|
|
27
|
-
}
|
|
28
|
-
return out;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
export function normalizeAllowedModel(model, providerId) {
|
|
32
|
-
let s = String(model || "").trim();
|
|
33
|
-
if (!s) return "";
|
|
34
|
-
const idx = s.indexOf("/");
|
|
35
|
-
if (idx > 0) {
|
|
36
|
-
const head = s.slice(0, idx).toLowerCase();
|
|
37
|
-
if (head === String(providerId || "").toLowerCase()) s = s.slice(idx + 1);
|
|
38
|
-
}
|
|
39
|
-
return s;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
export function normalizeEndpointPath(v) {
|
|
43
|
-
const s = String(v || "").trim();
|
|
44
|
-
if (!s) return "";
|
|
45
|
-
return s.startsWith("/") ? s : `/${s}`;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
export function defaultModelsPath(id) {
|
|
49
|
-
if (String(id).toLowerCase() === "workbuddy") return "/v3/config"; // 2026-10-10 现网实测:console 目录对多数 token 恒 401/500,产品文档 /v3/config 才稳定 200(与 globalworkbuddy 同源)
|
|
50
|
-
return "/models";
|
|
51
|
-
}
|
|
52
|
-
export function defaultChatPath(id) {
|
|
53
|
-
if (String(id).toLowerCase() === "workbuddy") return "/v2/chat/completions";
|
|
54
|
-
return "/chat/completions";
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
export const providerKeyEnv = (id) => `MSLXDFF_${String(id || "").toUpperCase().replace(/[^A-Z0-9]/g, "_")}_KEY`;
|
|
58
|
-
export const providerBaseUrlEnv = (id) => `MSLXDFF_${String(id || "").toUpperCase().replace(/[^A-Z0-9]/g, "_")}_BASE_URL`;
|
|
59
|
-
|
|
60
|
-
/**
|
|
61
|
-
* validate 7 字段:baseUrl, keys, auths, allowedModels, modelsPath, chatPath(share 已删,key 随转发自动借出)
|
|
62
|
-
* 返回 {ok, clean, errors}
|
|
63
|
-
*/
|
|
64
|
-
export function validateProviderConfig(raw = {}, providerId = "") {
|
|
65
|
-
const errors = [];
|
|
66
|
-
const clean = {};
|
|
67
|
-
// baseUrl
|
|
68
|
-
const baseUrl = normalizeBaseUrl(raw.baseUrl);
|
|
69
|
-
if (raw.baseUrl !== undefined && raw.baseUrl !== null && String(raw.baseUrl).trim() && !baseUrl) errors.push("baseUrl 不能为空");
|
|
70
|
-
clean.baseUrl = baseUrl;
|
|
71
|
-
// keys
|
|
72
|
-
const keys = [...new Set((Array.isArray(raw.keys) ? raw.keys : []).map((k) => String(k || "").trim()).filter(Boolean))];
|
|
73
|
-
clean.keys = keys;
|
|
74
|
-
// auths
|
|
75
|
-
clean.auths = normalizeAuths(raw.auths);
|
|
76
|
-
// allowedModels
|
|
77
|
-
const allowed = [...new Set((Array.isArray(raw.allowedModels) ? raw.allowedModels : []).map((m) => normalizeAllowedModel(m, providerId)).filter(Boolean))];
|
|
78
|
-
clean.allowedModels = allowed;
|
|
79
|
-
// modelsPath / chatPath
|
|
80
|
-
clean.modelsPath = raw.modelsPath === undefined ? undefined : (String(raw.modelsPath).trim() ? normalizeEndpointPath(raw.modelsPath) : "");
|
|
81
|
-
clean.chatPath = raw.chatPath === undefined ? undefined : (String(raw.chatPath).trim() ? normalizeEndpointPath(raw.chatPath) : "");
|
|
82
|
-
// 若 baseUrl 非空但不是 url 形态,弱校验:必须含 . 或 ://
|
|
83
|
-
if (clean.baseUrl && !/^https?:\/\//.test(clean.baseUrl) && !clean.baseUrl.includes(".")) {
|
|
84
|
-
// workbuddy 默认 https 已过,此分支仅防空
|
|
85
|
-
}
|
|
86
|
-
return { ok: errors.length === 0, clean, errors };
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
/**
|
|
90
|
-
* normalize 保持与 validate 一致,但不返回 ok,直接回 clean(用于落盘前归一)
|
|
91
|
-
*/
|
|
92
|
-
export function normalizeProviderConfig(raw = {}, providerId = "") {
|
|
93
|
-
const { clean } = validateProviderConfig(raw, providerId);
|
|
94
|
-
return clean;
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
/**
|
|
98
|
-
* merge 三级:env > file > legacy
|
|
99
|
-
* env: {baseUrl, keys} 来自 process.env
|
|
100
|
-
* file: 来自 providerConfigs[id]
|
|
101
|
-
* legacy: 来自 providerKeys[id](字符串或数组)
|
|
102
|
-
*/
|
|
103
|
-
export function mergeProviderConfig({ env = {}, file = {}, legacy = [] } = {}, providerId = "") {
|
|
104
|
-
// env 优先
|
|
105
|
-
const envKeys = Array.isArray(env.keys) ? [...new Set(env.keys.filter(Boolean))] : [];
|
|
106
|
-
const hasEnv = !!(env.baseUrl || envKeys.length);
|
|
107
|
-
if (hasEnv) {
|
|
108
|
-
return {
|
|
109
|
-
baseUrl: normalizeBaseUrl(env.baseUrl || file.baseUrl || ""),
|
|
110
|
-
keys: envKeys.length ? envKeys : (Array.isArray(file.keys) ? file.keys : []),
|
|
111
|
-
// env 模式下 file 的 allowed/auths 仍透传(与原 loadProviderConfig 一致)
|
|
112
|
-
allowedModels: Array.isArray(file.allowedModels) ? [...new Set(file.allowedModels.map((m) => normalizeAllowedModel(m, providerId)).filter(Boolean))] : [],
|
|
113
|
-
auths: normalizeAuths(file.auths),
|
|
114
|
-
modelsPath: file.modelsPath ? normalizeEndpointPath(file.modelsPath) : defaultModelsPath(providerId),
|
|
115
|
-
chatPath: file.chatPath ? normalizeEndpointPath(file.chatPath) : defaultChatPath(providerId),
|
|
116
|
-
};
|
|
117
|
-
}
|
|
118
|
-
// file 优先
|
|
119
|
-
if (file && typeof file === "object" && (file.baseUrl || (Array.isArray(file.keys) && file.keys.length) || file.allowedModels || file.auths)) {
|
|
120
|
-
return {
|
|
121
|
-
baseUrl: normalizeBaseUrl(file.baseUrl || ""),
|
|
122
|
-
keys: Array.isArray(file.keys) ? [...new Set(file.keys.filter(Boolean))] : [],
|
|
123
|
-
allowedModels: Array.isArray(file.allowedModels) ? [...new Set(file.allowedModels.map((m) => normalizeAllowedModel(m, providerId)).filter(Boolean))] : [],
|
|
124
|
-
auths: normalizeAuths(file.auths),
|
|
125
|
-
modelsPath: file.modelsPath ? normalizeEndpointPath(file.modelsPath) : defaultModelsPath(providerId),
|
|
126
|
-
chatPath: file.chatPath ? normalizeEndpointPath(file.chatPath) : defaultChatPath(providerId),
|
|
127
|
-
};
|
|
128
|
-
}
|
|
129
|
-
// legacy
|
|
130
|
-
const legacyKeys = Array.isArray(legacy) ? [...new Set(legacy.filter((k) => typeof k === "string" && k.trim().length))] : (typeof legacy === "string" && legacy.trim() ? [legacy.trim()] : []);
|
|
131
|
-
if (legacyKeys.length) {
|
|
132
|
-
return {
|
|
133
|
-
baseUrl: String(providerId).toLowerCase() === "workbuddy" ? WORKBUDDY_DEFAULT_BASE_URL : "",
|
|
134
|
-
keys: legacyKeys,
|
|
135
|
-
allowedModels: [],
|
|
136
|
-
auths: [],
|
|
137
|
-
modelsPath: defaultModelsPath(providerId),
|
|
138
|
-
chatPath: defaultChatPath(providerId),
|
|
139
|
-
};
|
|
140
|
-
}
|
|
141
|
-
return { baseUrl: "", keys: [], allowedModels: [], auths: [], modelsPath: defaultModelsPath(providerId), chatPath: defaultChatPath(providerId) };
|
|
142
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* ProviderConfig 深模块:7 字段 validate/normalize/merge 单点
|
|
3
|
+
* 纯函数,不读 env/file,直接由调用方注入三级数据
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export const WORKBUDDY_DEFAULT_BASE_URL = "https://copilot.tencent.com";
|
|
7
|
+
|
|
8
|
+
export function normalizeBaseUrl(url) {
|
|
9
|
+
const s = String(url || "").trim();
|
|
10
|
+
if (!s) return "";
|
|
11
|
+
return s.replace(/\/+$/, "");
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function normalizeAuths(list) {
|
|
15
|
+
if (!Array.isArray(list)) return [];
|
|
16
|
+
const out = [];
|
|
17
|
+
for (const a of list) {
|
|
18
|
+
if (!a || typeof a !== "object") continue;
|
|
19
|
+
const uid = String(a.uid || a.userId || "").trim();
|
|
20
|
+
if (!uid) continue;
|
|
21
|
+
out.push({
|
|
22
|
+
uid,
|
|
23
|
+
domain: String(a.domain || "www.codebuddy.cn").trim() || "www.codebuddy.cn",
|
|
24
|
+
enterpriseId: String(a.enterpriseId || a.enterprise_id || "").trim(),
|
|
25
|
+
refreshToken: String(a.refreshToken || a.refresh_token || "").trim(),
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
return out;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function normalizeAllowedModel(model, providerId) {
|
|
32
|
+
let s = String(model || "").trim();
|
|
33
|
+
if (!s) return "";
|
|
34
|
+
const idx = s.indexOf("/");
|
|
35
|
+
if (idx > 0) {
|
|
36
|
+
const head = s.slice(0, idx).toLowerCase();
|
|
37
|
+
if (head === String(providerId || "").toLowerCase()) s = s.slice(idx + 1);
|
|
38
|
+
}
|
|
39
|
+
return s;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function normalizeEndpointPath(v) {
|
|
43
|
+
const s = String(v || "").trim();
|
|
44
|
+
if (!s) return "";
|
|
45
|
+
return s.startsWith("/") ? s : `/${s}`;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function defaultModelsPath(id) {
|
|
49
|
+
if (String(id).toLowerCase() === "workbuddy") return "/v3/config"; // 2026-10-10 现网实测:console 目录对多数 token 恒 401/500,产品文档 /v3/config 才稳定 200(与 globalworkbuddy 同源)
|
|
50
|
+
return "/models";
|
|
51
|
+
}
|
|
52
|
+
export function defaultChatPath(id) {
|
|
53
|
+
if (String(id).toLowerCase() === "workbuddy") return "/v2/chat/completions";
|
|
54
|
+
return "/chat/completions";
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export const providerKeyEnv = (id) => `MSLXDFF_${String(id || "").toUpperCase().replace(/[^A-Z0-9]/g, "_")}_KEY`;
|
|
58
|
+
export const providerBaseUrlEnv = (id) => `MSLXDFF_${String(id || "").toUpperCase().replace(/[^A-Z0-9]/g, "_")}_BASE_URL`;
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* validate 7 字段:baseUrl, keys, auths, allowedModels, modelsPath, chatPath(share 已删,key 随转发自动借出)
|
|
62
|
+
* 返回 {ok, clean, errors}
|
|
63
|
+
*/
|
|
64
|
+
export function validateProviderConfig(raw = {}, providerId = "") {
|
|
65
|
+
const errors = [];
|
|
66
|
+
const clean = {};
|
|
67
|
+
// baseUrl
|
|
68
|
+
const baseUrl = normalizeBaseUrl(raw.baseUrl);
|
|
69
|
+
if (raw.baseUrl !== undefined && raw.baseUrl !== null && String(raw.baseUrl).trim() && !baseUrl) errors.push("baseUrl 不能为空");
|
|
70
|
+
clean.baseUrl = baseUrl;
|
|
71
|
+
// keys
|
|
72
|
+
const keys = [...new Set((Array.isArray(raw.keys) ? raw.keys : []).map((k) => String(k || "").trim()).filter(Boolean))];
|
|
73
|
+
clean.keys = keys;
|
|
74
|
+
// auths
|
|
75
|
+
clean.auths = normalizeAuths(raw.auths);
|
|
76
|
+
// allowedModels
|
|
77
|
+
const allowed = [...new Set((Array.isArray(raw.allowedModels) ? raw.allowedModels : []).map((m) => normalizeAllowedModel(m, providerId)).filter(Boolean))];
|
|
78
|
+
clean.allowedModels = allowed;
|
|
79
|
+
// modelsPath / chatPath
|
|
80
|
+
clean.modelsPath = raw.modelsPath === undefined ? undefined : (String(raw.modelsPath).trim() ? normalizeEndpointPath(raw.modelsPath) : "");
|
|
81
|
+
clean.chatPath = raw.chatPath === undefined ? undefined : (String(raw.chatPath).trim() ? normalizeEndpointPath(raw.chatPath) : "");
|
|
82
|
+
// 若 baseUrl 非空但不是 url 形态,弱校验:必须含 . 或 ://
|
|
83
|
+
if (clean.baseUrl && !/^https?:\/\//.test(clean.baseUrl) && !clean.baseUrl.includes(".")) {
|
|
84
|
+
// workbuddy 默认 https 已过,此分支仅防空
|
|
85
|
+
}
|
|
86
|
+
return { ok: errors.length === 0, clean, errors };
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* normalize 保持与 validate 一致,但不返回 ok,直接回 clean(用于落盘前归一)
|
|
91
|
+
*/
|
|
92
|
+
export function normalizeProviderConfig(raw = {}, providerId = "") {
|
|
93
|
+
const { clean } = validateProviderConfig(raw, providerId);
|
|
94
|
+
return clean;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* merge 三级:env > file > legacy
|
|
99
|
+
* env: {baseUrl, keys} 来自 process.env
|
|
100
|
+
* file: 来自 providerConfigs[id]
|
|
101
|
+
* legacy: 来自 providerKeys[id](字符串或数组)
|
|
102
|
+
*/
|
|
103
|
+
export function mergeProviderConfig({ env = {}, file = {}, legacy = [] } = {}, providerId = "") {
|
|
104
|
+
// env 优先
|
|
105
|
+
const envKeys = Array.isArray(env.keys) ? [...new Set(env.keys.filter(Boolean))] : [];
|
|
106
|
+
const hasEnv = !!(env.baseUrl || envKeys.length);
|
|
107
|
+
if (hasEnv) {
|
|
108
|
+
return {
|
|
109
|
+
baseUrl: normalizeBaseUrl(env.baseUrl || file.baseUrl || ""),
|
|
110
|
+
keys: envKeys.length ? envKeys : (Array.isArray(file.keys) ? file.keys : []),
|
|
111
|
+
// env 模式下 file 的 allowed/auths 仍透传(与原 loadProviderConfig 一致)
|
|
112
|
+
allowedModels: Array.isArray(file.allowedModels) ? [...new Set(file.allowedModels.map((m) => normalizeAllowedModel(m, providerId)).filter(Boolean))] : [],
|
|
113
|
+
auths: normalizeAuths(file.auths),
|
|
114
|
+
modelsPath: file.modelsPath ? normalizeEndpointPath(file.modelsPath) : defaultModelsPath(providerId),
|
|
115
|
+
chatPath: file.chatPath ? normalizeEndpointPath(file.chatPath) : defaultChatPath(providerId),
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
// file 优先
|
|
119
|
+
if (file && typeof file === "object" && (file.baseUrl || (Array.isArray(file.keys) && file.keys.length) || file.allowedModels || file.auths)) {
|
|
120
|
+
return {
|
|
121
|
+
baseUrl: normalizeBaseUrl(file.baseUrl || ""),
|
|
122
|
+
keys: Array.isArray(file.keys) ? [...new Set(file.keys.filter(Boolean))] : [],
|
|
123
|
+
allowedModels: Array.isArray(file.allowedModels) ? [...new Set(file.allowedModels.map((m) => normalizeAllowedModel(m, providerId)).filter(Boolean))] : [],
|
|
124
|
+
auths: normalizeAuths(file.auths),
|
|
125
|
+
modelsPath: file.modelsPath ? normalizeEndpointPath(file.modelsPath) : defaultModelsPath(providerId),
|
|
126
|
+
chatPath: file.chatPath ? normalizeEndpointPath(file.chatPath) : defaultChatPath(providerId),
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
// legacy
|
|
130
|
+
const legacyKeys = Array.isArray(legacy) ? [...new Set(legacy.filter((k) => typeof k === "string" && k.trim().length))] : (typeof legacy === "string" && legacy.trim() ? [legacy.trim()] : []);
|
|
131
|
+
if (legacyKeys.length) {
|
|
132
|
+
return {
|
|
133
|
+
baseUrl: String(providerId).toLowerCase() === "workbuddy" ? WORKBUDDY_DEFAULT_BASE_URL : "",
|
|
134
|
+
keys: legacyKeys,
|
|
135
|
+
allowedModels: [],
|
|
136
|
+
auths: [],
|
|
137
|
+
modelsPath: defaultModelsPath(providerId),
|
|
138
|
+
chatPath: defaultChatPath(providerId),
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
return { baseUrl: "", keys: [], allowedModels: [], auths: [], modelsPath: defaultModelsPath(providerId), chatPath: defaultChatPath(providerId) };
|
|
142
|
+
}
|
|
@@ -1,31 +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
|
-
}
|
|
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
|
+
}
|
|
@@ -1,94 +1,94 @@
|
|
|
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
|
-
const STATS_ALPHA = 0.3;
|
|
34
|
-
function ema(prev, next) {
|
|
35
|
-
if (!Number.isFinite(prev) || prev <= 0) return Math.round(next);
|
|
36
|
-
if (!Number.isFinite(next) || next <= 0) return Math.round(prev);
|
|
37
|
-
return Math.round(prev * (1 - STATS_ALPHA) + next * STATS_ALPHA);
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
export function recordModelStats(id, { ttfbMs, totalMs, tps, completionTokens, file = defaultStateFile() } = {}) {
|
|
41
|
-
if (!id || typeof id !== "string") return null;
|
|
42
|
-
const stats = loadModelStats({ file });
|
|
43
|
-
const cur = stats[id] || { count: 0 };
|
|
44
|
-
const next = { ...cur };
|
|
45
|
-
next.count = (cur.count || 0) + 1;
|
|
46
|
-
next.lastAt = Date.now();
|
|
47
|
-
if (Number.isFinite(ttfbMs) && ttfbMs >= 0) {
|
|
48
|
-
next.avgTtfbMs = cur.avgTtfbMs != null ? ema(cur.avgTtfbMs, ttfbMs) : Math.round(ttfbMs);
|
|
49
|
-
next.emaTtfbMs = next.avgTtfbMs;
|
|
50
|
-
// 简易 p95:取 max 的 EMA
|
|
51
|
-
if (cur.p95Ttfb == null) next.p95Ttfb = Math.round(ttfbMs);
|
|
52
|
-
else next.p95Ttfb = Math.max(cur.p95Ttfb, Math.round(ttfbMs * 0.7 + cur.p95Ttfb * 0.3));
|
|
53
|
-
}
|
|
54
|
-
if (Number.isFinite(totalMs) && totalMs >= 0) {
|
|
55
|
-
next.avgTotalMs = cur.avgTotalMs != null ? ema(cur.avgTotalMs, totalMs) : Math.round(totalMs);
|
|
56
|
-
next.emaTotalMs = next.avgTotalMs;
|
|
57
|
-
next.lastTotalMs = Math.round(totalMs);
|
|
58
|
-
}
|
|
59
|
-
if (Number.isFinite(tps) && tps > 0) {
|
|
60
|
-
next.avgTps = cur.avgTps != null ? Number((cur.avgTps * (1 - STATS_ALPHA) + tps * STATS_ALPHA).toFixed(1)) : Number(tps.toFixed(1));
|
|
61
|
-
next.emaTps = next.avgTps;
|
|
62
|
-
}
|
|
63
|
-
if (Number.isFinite(completionTokens) && completionTokens > 0) {
|
|
64
|
-
const prevAvg = cur.avgCompTok;
|
|
65
|
-
next.avgCompTok = prevAvg != null ? Math.round(prevAvg * (1 - STATS_ALPHA) + completionTokens * STATS_ALPHA) : Math.round(completionTokens);
|
|
66
|
-
}
|
|
67
|
-
// 兼容旧字段:lastAt 供排序
|
|
68
|
-
stats[id] = next;
|
|
69
|
-
// 监控需实时可见,用同步落盘而非 500ms debounce
|
|
70
|
-
try { writeStateImmediate(file, { modelStats: stats }); } catch { saveModelStats(stats, { file }); }
|
|
71
|
-
return next;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
export function loadPreferredModel({ file = defaultStateFile() } = {}) {
|
|
75
|
-
const v = readState(file).preferredModel;
|
|
76
|
-
return typeof v === "string" && v.trim() ? v.trim() : null;
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
export function loadModelPicks({ file = defaultStateFile() } = {}) {
|
|
80
|
-
const picks = readState(file).modelPicks;
|
|
81
|
-
if (!Array.isArray(picks)) return [];
|
|
82
|
-
return [...new Set(picks.filter((x) => typeof x === "string" && x.trim().length))];
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
export function saveModelPicks(picks, { file = defaultStateFile() } = {}) {
|
|
86
|
-
const list = [...new Set((Array.isArray(picks) ? picks : []).filter((x) => typeof x === "string" && x.trim().length))];
|
|
87
|
-
writeStateImmediate(file, { modelPicks: list });
|
|
88
|
-
return list;
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
export function savePreferredModel(id, { file = defaultStateFile() } = {}) {
|
|
92
|
-
writeStateImmediate(file, { preferredModel: String(id || "").trim() });
|
|
93
|
-
return String(id || "").trim();
|
|
94
|
-
}
|
|
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
|
+
const STATS_ALPHA = 0.3;
|
|
34
|
+
function ema(prev, next) {
|
|
35
|
+
if (!Number.isFinite(prev) || prev <= 0) return Math.round(next);
|
|
36
|
+
if (!Number.isFinite(next) || next <= 0) return Math.round(prev);
|
|
37
|
+
return Math.round(prev * (1 - STATS_ALPHA) + next * STATS_ALPHA);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function recordModelStats(id, { ttfbMs, totalMs, tps, completionTokens, file = defaultStateFile() } = {}) {
|
|
41
|
+
if (!id || typeof id !== "string") return null;
|
|
42
|
+
const stats = loadModelStats({ file });
|
|
43
|
+
const cur = stats[id] || { count: 0 };
|
|
44
|
+
const next = { ...cur };
|
|
45
|
+
next.count = (cur.count || 0) + 1;
|
|
46
|
+
next.lastAt = Date.now();
|
|
47
|
+
if (Number.isFinite(ttfbMs) && ttfbMs >= 0) {
|
|
48
|
+
next.avgTtfbMs = cur.avgTtfbMs != null ? ema(cur.avgTtfbMs, ttfbMs) : Math.round(ttfbMs);
|
|
49
|
+
next.emaTtfbMs = next.avgTtfbMs;
|
|
50
|
+
// 简易 p95:取 max 的 EMA
|
|
51
|
+
if (cur.p95Ttfb == null) next.p95Ttfb = Math.round(ttfbMs);
|
|
52
|
+
else next.p95Ttfb = Math.max(cur.p95Ttfb, Math.round(ttfbMs * 0.7 + cur.p95Ttfb * 0.3));
|
|
53
|
+
}
|
|
54
|
+
if (Number.isFinite(totalMs) && totalMs >= 0) {
|
|
55
|
+
next.avgTotalMs = cur.avgTotalMs != null ? ema(cur.avgTotalMs, totalMs) : Math.round(totalMs);
|
|
56
|
+
next.emaTotalMs = next.avgTotalMs;
|
|
57
|
+
next.lastTotalMs = Math.round(totalMs);
|
|
58
|
+
}
|
|
59
|
+
if (Number.isFinite(tps) && tps > 0) {
|
|
60
|
+
next.avgTps = cur.avgTps != null ? Number((cur.avgTps * (1 - STATS_ALPHA) + tps * STATS_ALPHA).toFixed(1)) : Number(tps.toFixed(1));
|
|
61
|
+
next.emaTps = next.avgTps;
|
|
62
|
+
}
|
|
63
|
+
if (Number.isFinite(completionTokens) && completionTokens > 0) {
|
|
64
|
+
const prevAvg = cur.avgCompTok;
|
|
65
|
+
next.avgCompTok = prevAvg != null ? Math.round(prevAvg * (1 - STATS_ALPHA) + completionTokens * STATS_ALPHA) : Math.round(completionTokens);
|
|
66
|
+
}
|
|
67
|
+
// 兼容旧字段:lastAt 供排序
|
|
68
|
+
stats[id] = next;
|
|
69
|
+
// 监控需实时可见,用同步落盘而非 500ms debounce
|
|
70
|
+
try { writeStateImmediate(file, { modelStats: stats }); } catch { saveModelStats(stats, { file }); }
|
|
71
|
+
return next;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export function loadPreferredModel({ file = defaultStateFile() } = {}) {
|
|
75
|
+
const v = readState(file).preferredModel;
|
|
76
|
+
return typeof v === "string" && v.trim() ? v.trim() : null;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export function loadModelPicks({ file = defaultStateFile() } = {}) {
|
|
80
|
+
const picks = readState(file).modelPicks;
|
|
81
|
+
if (!Array.isArray(picks)) return [];
|
|
82
|
+
return [...new Set(picks.filter((x) => typeof x === "string" && x.trim().length))];
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export function saveModelPicks(picks, { file = defaultStateFile() } = {}) {
|
|
86
|
+
const list = [...new Set((Array.isArray(picks) ? picks : []).filter((x) => typeof x === "string" && x.trim().length))];
|
|
87
|
+
writeStateImmediate(file, { modelPicks: list });
|
|
88
|
+
return list;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export function savePreferredModel(id, { file = defaultStateFile() } = {}) {
|
|
92
|
+
writeStateImmediate(file, { preferredModel: String(id || "").trim() });
|
|
93
|
+
return String(id || "").trim();
|
|
94
|
+
}
|