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/logs.js
CHANGED
|
@@ -1,195 +1,195 @@
|
|
|
1
|
-
import { appendFileSync, readFileSync, mkdirSync, existsSync, writeFileSync, statSync } from "node:fs";
|
|
2
|
-
import { appendFile, stat, readFile, writeFile } from "node:fs/promises";
|
|
3
|
-
import { dirname, join } from "node:path";
|
|
4
|
-
import os from "node:os";
|
|
5
|
-
import { defaultStateFile } from "./state.js";
|
|
6
|
-
import { fmtShanghaiYMDHMS } from "./time.js";
|
|
7
|
-
|
|
8
|
-
// 日志桶体积上限。events.log 是排查主入口(`-log`/`-debug` 都读它),1MB ≈ 1000 行
|
|
9
|
-
// ≈ 3 分钟窗口,事后回溯基本不可用;同时旧的 `slice(-100)` 回退策略与 1MB 阈值严重不匹配
|
|
10
|
-
// ——一次裁剪丢弃 90% 容量(实测 1043 行 → 99 行)。阈值提到 4MB、回退按字节保留末尾 3/4,
|
|
11
|
-
// 两者配平后覆盖窗口约 10 倍于改前,且不再有断崖。
|
|
12
|
-
const MAX_BYTES = 4 * 1024 * 1024;
|
|
13
|
-
const KEEP_RATIO = 3 / 4;
|
|
14
|
-
|
|
15
|
-
export function logDir() {
|
|
16
|
-
return process.env.MSLXDFF_DAEMON_DIR || dirname(defaultStateFile());
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
export function callsFile() {
|
|
20
|
-
return join(logDir(), "calls.log");
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
export function errorsFile() {
|
|
24
|
-
return join(logDir(), "errors.log");
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
export function eventsFile() {
|
|
28
|
-
return join(logDir(), "events.log");
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
export function timelineFile() {
|
|
32
|
-
return join(logDir(), "timeline.log");
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
function ensureDir(dir) {
|
|
36
|
-
mkdirSync(dir, { recursive: true });
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
/**
|
|
40
|
-
* 按字节从尾部回退(不截半行):累积整行直到超出预算,丢弃更早的行。
|
|
41
|
-
* 用 `lines.slice(-N)` 之类的**行数**回退无法与**字节**阈值配平——行有长有短(events 行含 detail
|
|
42
|
-
* 对象,实测均值 ~1KB),固定行数等于固定容量,一次裁剪就是断崖。
|
|
43
|
-
*/
|
|
44
|
-
function keepTailByBytes(text, maxBytes) {
|
|
45
|
-
const limit = Math.max(1, Math.floor(maxBytes * KEEP_RATIO));
|
|
46
|
-
if (Buffer.byteLength(text) <= limit) return text;
|
|
47
|
-
const lines = text.split("\n");
|
|
48
|
-
let bytes = 0;
|
|
49
|
-
let i = lines.length - 1;
|
|
50
|
-
for (; i >= 0; i--) {
|
|
51
|
-
const b = Buffer.byteLength(lines[i]) + 1;
|
|
52
|
-
if (bytes + b > limit) break;
|
|
53
|
-
bytes += b;
|
|
54
|
-
}
|
|
55
|
-
// 至少保留最后一条:单行本身超过 limit 时(超大 detail/堆栈)上面的循环会一路退到第一条内容行之前,
|
|
56
|
-
// 返回空串 = 整个日志被清空,比截断更糟(一次丢掉全部历史,且看起来像"日志坏了")。
|
|
57
|
-
// 判据是「保留结果为空」而不是 `i === lines.length-1`:文件以换行结尾时 split 出来的最后一个元素是
|
|
58
|
-
// 空串,`lines.length-1` 指向它,与真正的内容行不同(这一处曾写错,实测保底从未生效)。
|
|
59
|
-
// 宁可让这一条略微超出预算,也要留住"最新发生了什么"。
|
|
60
|
-
const kept = lines.slice(i + 1).join("\n");
|
|
61
|
-
if (!kept && lines.length) {
|
|
62
|
-
const last = lines.filter((l) => l !== "");
|
|
63
|
-
if (last.length) return last[last.length - 1];
|
|
64
|
-
}
|
|
65
|
-
return kept;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
function trimIfOversized(file, maxBytes = MAX_BYTES) {
|
|
69
|
-
try {
|
|
70
|
-
const st = statSync(file);
|
|
71
|
-
if (st.size <= maxBytes) return;
|
|
72
|
-
writeFileSync(file, keepTailByBytes(readFileSync(file, "utf8"), maxBytes));
|
|
73
|
-
} catch {
|
|
74
|
-
// ignore
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
async function trimIfOversizedAsync(file, maxBytes = MAX_BYTES) {
|
|
79
|
-
try {
|
|
80
|
-
const st = await stat(file);
|
|
81
|
-
if (st.size <= maxBytes) return;
|
|
82
|
-
await writeFile(file, keepTailByBytes(await readFile(file, "utf8"), maxBytes));
|
|
83
|
-
} catch {
|
|
84
|
-
// ignore
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
function shouldSync(file) {
|
|
89
|
-
if (process.env.MSLXDFF_LOGS_SYNC === "1") return true;
|
|
90
|
-
if (process.env.MSLXDFF_DAEMON_DIR) {
|
|
91
|
-
const dir = process.env.MSLXDFF_DAEMON_DIR;
|
|
92
|
-
if (file.startsWith(dir)) return true;
|
|
93
|
-
}
|
|
94
|
-
// 显式 tmp 文件(所有 test 的 mkdtemp 前缀)走同步,保证 read-after-write 可见
|
|
95
|
-
const low = file.toLowerCase();
|
|
96
|
-
if (low.includes("mslxdff-") || low.includes("tmp") || low.includes("temp")) return true;
|
|
97
|
-
// 非默认目录的文件一律同步(测试传入的临时路径)
|
|
98
|
-
try {
|
|
99
|
-
const def = logDir().toLowerCase();
|
|
100
|
-
if (!low.startsWith(def)) return true;
|
|
101
|
-
} catch {}
|
|
102
|
-
return false;
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
function appendLine(file, entry) {
|
|
106
|
-
ensureDir(dirname(file));
|
|
107
|
-
const line = JSON.stringify({ ts: fmtShanghaiYMDHMS(new Date()), ...entry }) + "\n";
|
|
108
|
-
if (shouldSync(file)) {
|
|
109
|
-
appendFileSync(file, line);
|
|
110
|
-
trimIfOversized(file);
|
|
111
|
-
return;
|
|
112
|
-
}
|
|
113
|
-
// 线上异步:不阻塞事件循环
|
|
114
|
-
appendFile(file, line)
|
|
115
|
-
.catch(() => {})
|
|
116
|
-
.then(() => trimIfOversizedAsync(file).catch(() => {}));
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
export function recentTimeline(n = 10, { file = timelineFile() } = {}) {
|
|
120
|
-
try {
|
|
121
|
-
if (!existsSync(file)) return [];
|
|
122
|
-
return readFileSync(file, "utf8").split("\n").filter(Boolean).slice(-n);
|
|
123
|
-
} catch { return []; }
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
export function appendCall(entry, { file = callsFile() } = {}) {
|
|
127
|
-
appendLine(file, entry);
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
export function appendError(entry, { file = errorsFile() } = {}) {
|
|
131
|
-
appendLine(file, entry);
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
// Structured debug event stream: one JSON line per request/error/forward
|
|
135
|
-
// step, consumed live by `mslxdff -debug`.
|
|
136
|
-
export function appendEvent(entry, { file = eventsFile() } = {}) {
|
|
137
|
-
appendLine(file, dropTalk(entry));
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
// events.log 是 debug 流(1MB 环形),对话正文只准进 talk/*.log。
|
|
141
|
-
// 单点剥掉 detail.talk:任何 relay 路径(含绕过 relay-pipeline 的 exhausted 收尾)都不会
|
|
142
|
-
// 把整段问答灌进 debug 行;留 talkChars 计数,debug 时仍看得出这轮有没有出字。
|
|
143
|
-
function dropTalk(entry) {
|
|
144
|
-
const t = entry?.detail?.talk;
|
|
145
|
-
if (!t) return entry;
|
|
146
|
-
const detail = { ...entry.detail };
|
|
147
|
-
delete detail.talk;
|
|
148
|
-
detail.talkChars = (t.content || []).join("").length;
|
|
149
|
-
detail.talkReasoningChars = (t.reasoning || []).join("").length;
|
|
150
|
-
return { ...entry, detail };
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
export function recentEvents(n = 100, { file = eventsFile() } = {}) {
|
|
154
|
-
return readLines(file).slice(-n);
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
function readLines(file) {
|
|
158
|
-
try {
|
|
159
|
-
if (!existsSync(file)) return [];
|
|
160
|
-
const text = readFileSync(file, "utf8");
|
|
161
|
-
return text.split("\n").filter(Boolean).map((l) => {
|
|
162
|
-
try {
|
|
163
|
-
return JSON.parse(l);
|
|
164
|
-
} catch {
|
|
165
|
-
return null;
|
|
166
|
-
}
|
|
167
|
-
}).filter(Boolean);
|
|
168
|
-
} catch {
|
|
169
|
-
return [];
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
export function recentCalls(n = 5, { file = callsFile() } = {}) {
|
|
174
|
-
return readLines(file).slice(-n);
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
export function lastError({ file = errorsFile() } = {}) {
|
|
178
|
-
const lines = readLines(file);
|
|
179
|
-
return lines.length ? lines[lines.length - 1] : null;
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
export function recentErrors(n = 5, { file = errorsFile() } = {}) {
|
|
183
|
-
return readLines(file).slice(-n);
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
export function appendTimeline(entry, { file = timelineFile() } = {}) {
|
|
187
|
-
const line = `${fmtShanghaiYMDHMS(new Date())} ${String(entry || "")}\n`;
|
|
188
|
-
ensureDir(dirname(file));
|
|
189
|
-
if (shouldSync(file)) {
|
|
190
|
-
appendFileSync(file, line);
|
|
191
|
-
trimIfOversized(file);
|
|
192
|
-
return;
|
|
193
|
-
}
|
|
194
|
-
appendFile(file, line).catch(() => {}).then(() => trimIfOversizedAsync(file).catch(() => {}));
|
|
195
|
-
}
|
|
1
|
+
import { appendFileSync, readFileSync, mkdirSync, existsSync, writeFileSync, statSync } from "node:fs";
|
|
2
|
+
import { appendFile, stat, readFile, writeFile } from "node:fs/promises";
|
|
3
|
+
import { dirname, join } from "node:path";
|
|
4
|
+
import os from "node:os";
|
|
5
|
+
import { defaultStateFile } from "./state.js";
|
|
6
|
+
import { fmtShanghaiYMDHMS } from "./time.js";
|
|
7
|
+
|
|
8
|
+
// 日志桶体积上限。events.log 是排查主入口(`-log`/`-debug` 都读它),1MB ≈ 1000 行
|
|
9
|
+
// ≈ 3 分钟窗口,事后回溯基本不可用;同时旧的 `slice(-100)` 回退策略与 1MB 阈值严重不匹配
|
|
10
|
+
// ——一次裁剪丢弃 90% 容量(实测 1043 行 → 99 行)。阈值提到 4MB、回退按字节保留末尾 3/4,
|
|
11
|
+
// 两者配平后覆盖窗口约 10 倍于改前,且不再有断崖。
|
|
12
|
+
const MAX_BYTES = 4 * 1024 * 1024;
|
|
13
|
+
const KEEP_RATIO = 3 / 4;
|
|
14
|
+
|
|
15
|
+
export function logDir() {
|
|
16
|
+
return process.env.MSLXDFF_DAEMON_DIR || dirname(defaultStateFile());
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function callsFile() {
|
|
20
|
+
return join(logDir(), "calls.log");
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function errorsFile() {
|
|
24
|
+
return join(logDir(), "errors.log");
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function eventsFile() {
|
|
28
|
+
return join(logDir(), "events.log");
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function timelineFile() {
|
|
32
|
+
return join(logDir(), "timeline.log");
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function ensureDir(dir) {
|
|
36
|
+
mkdirSync(dir, { recursive: true });
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* 按字节从尾部回退(不截半行):累积整行直到超出预算,丢弃更早的行。
|
|
41
|
+
* 用 `lines.slice(-N)` 之类的**行数**回退无法与**字节**阈值配平——行有长有短(events 行含 detail
|
|
42
|
+
* 对象,实测均值 ~1KB),固定行数等于固定容量,一次裁剪就是断崖。
|
|
43
|
+
*/
|
|
44
|
+
function keepTailByBytes(text, maxBytes) {
|
|
45
|
+
const limit = Math.max(1, Math.floor(maxBytes * KEEP_RATIO));
|
|
46
|
+
if (Buffer.byteLength(text) <= limit) return text;
|
|
47
|
+
const lines = text.split("\n");
|
|
48
|
+
let bytes = 0;
|
|
49
|
+
let i = lines.length - 1;
|
|
50
|
+
for (; i >= 0; i--) {
|
|
51
|
+
const b = Buffer.byteLength(lines[i]) + 1;
|
|
52
|
+
if (bytes + b > limit) break;
|
|
53
|
+
bytes += b;
|
|
54
|
+
}
|
|
55
|
+
// 至少保留最后一条:单行本身超过 limit 时(超大 detail/堆栈)上面的循环会一路退到第一条内容行之前,
|
|
56
|
+
// 返回空串 = 整个日志被清空,比截断更糟(一次丢掉全部历史,且看起来像"日志坏了")。
|
|
57
|
+
// 判据是「保留结果为空」而不是 `i === lines.length-1`:文件以换行结尾时 split 出来的最后一个元素是
|
|
58
|
+
// 空串,`lines.length-1` 指向它,与真正的内容行不同(这一处曾写错,实测保底从未生效)。
|
|
59
|
+
// 宁可让这一条略微超出预算,也要留住"最新发生了什么"。
|
|
60
|
+
const kept = lines.slice(i + 1).join("\n");
|
|
61
|
+
if (!kept && lines.length) {
|
|
62
|
+
const last = lines.filter((l) => l !== "");
|
|
63
|
+
if (last.length) return last[last.length - 1];
|
|
64
|
+
}
|
|
65
|
+
return kept;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function trimIfOversized(file, maxBytes = MAX_BYTES) {
|
|
69
|
+
try {
|
|
70
|
+
const st = statSync(file);
|
|
71
|
+
if (st.size <= maxBytes) return;
|
|
72
|
+
writeFileSync(file, keepTailByBytes(readFileSync(file, "utf8"), maxBytes));
|
|
73
|
+
} catch {
|
|
74
|
+
// ignore
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
async function trimIfOversizedAsync(file, maxBytes = MAX_BYTES) {
|
|
79
|
+
try {
|
|
80
|
+
const st = await stat(file);
|
|
81
|
+
if (st.size <= maxBytes) return;
|
|
82
|
+
await writeFile(file, keepTailByBytes(await readFile(file, "utf8"), maxBytes));
|
|
83
|
+
} catch {
|
|
84
|
+
// ignore
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function shouldSync(file) {
|
|
89
|
+
if (process.env.MSLXDFF_LOGS_SYNC === "1") return true;
|
|
90
|
+
if (process.env.MSLXDFF_DAEMON_DIR) {
|
|
91
|
+
const dir = process.env.MSLXDFF_DAEMON_DIR;
|
|
92
|
+
if (file.startsWith(dir)) return true;
|
|
93
|
+
}
|
|
94
|
+
// 显式 tmp 文件(所有 test 的 mkdtemp 前缀)走同步,保证 read-after-write 可见
|
|
95
|
+
const low = file.toLowerCase();
|
|
96
|
+
if (low.includes("mslxdff-") || low.includes("tmp") || low.includes("temp")) return true;
|
|
97
|
+
// 非默认目录的文件一律同步(测试传入的临时路径)
|
|
98
|
+
try {
|
|
99
|
+
const def = logDir().toLowerCase();
|
|
100
|
+
if (!low.startsWith(def)) return true;
|
|
101
|
+
} catch {}
|
|
102
|
+
return false;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function appendLine(file, entry) {
|
|
106
|
+
ensureDir(dirname(file));
|
|
107
|
+
const line = JSON.stringify({ ts: fmtShanghaiYMDHMS(new Date()), ...entry }) + "\n";
|
|
108
|
+
if (shouldSync(file)) {
|
|
109
|
+
appendFileSync(file, line);
|
|
110
|
+
trimIfOversized(file);
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
// 线上异步:不阻塞事件循环
|
|
114
|
+
appendFile(file, line)
|
|
115
|
+
.catch(() => {})
|
|
116
|
+
.then(() => trimIfOversizedAsync(file).catch(() => {}));
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export function recentTimeline(n = 10, { file = timelineFile() } = {}) {
|
|
120
|
+
try {
|
|
121
|
+
if (!existsSync(file)) return [];
|
|
122
|
+
return readFileSync(file, "utf8").split("\n").filter(Boolean).slice(-n);
|
|
123
|
+
} catch { return []; }
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export function appendCall(entry, { file = callsFile() } = {}) {
|
|
127
|
+
appendLine(file, entry);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
export function appendError(entry, { file = errorsFile() } = {}) {
|
|
131
|
+
appendLine(file, entry);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Structured debug event stream: one JSON line per request/error/forward
|
|
135
|
+
// step, consumed live by `mslxdff -debug`.
|
|
136
|
+
export function appendEvent(entry, { file = eventsFile() } = {}) {
|
|
137
|
+
appendLine(file, dropTalk(entry));
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// events.log 是 debug 流(1MB 环形),对话正文只准进 talk/*.log。
|
|
141
|
+
// 单点剥掉 detail.talk:任何 relay 路径(含绕过 relay-pipeline 的 exhausted 收尾)都不会
|
|
142
|
+
// 把整段问答灌进 debug 行;留 talkChars 计数,debug 时仍看得出这轮有没有出字。
|
|
143
|
+
function dropTalk(entry) {
|
|
144
|
+
const t = entry?.detail?.talk;
|
|
145
|
+
if (!t) return entry;
|
|
146
|
+
const detail = { ...entry.detail };
|
|
147
|
+
delete detail.talk;
|
|
148
|
+
detail.talkChars = (t.content || []).join("").length;
|
|
149
|
+
detail.talkReasoningChars = (t.reasoning || []).join("").length;
|
|
150
|
+
return { ...entry, detail };
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
export function recentEvents(n = 100, { file = eventsFile() } = {}) {
|
|
154
|
+
return readLines(file).slice(-n);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function readLines(file) {
|
|
158
|
+
try {
|
|
159
|
+
if (!existsSync(file)) return [];
|
|
160
|
+
const text = readFileSync(file, "utf8");
|
|
161
|
+
return text.split("\n").filter(Boolean).map((l) => {
|
|
162
|
+
try {
|
|
163
|
+
return JSON.parse(l);
|
|
164
|
+
} catch {
|
|
165
|
+
return null;
|
|
166
|
+
}
|
|
167
|
+
}).filter(Boolean);
|
|
168
|
+
} catch {
|
|
169
|
+
return [];
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
export function recentCalls(n = 5, { file = callsFile() } = {}) {
|
|
174
|
+
return readLines(file).slice(-n);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
export function lastError({ file = errorsFile() } = {}) {
|
|
178
|
+
const lines = readLines(file);
|
|
179
|
+
return lines.length ? lines[lines.length - 1] : null;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
export function recentErrors(n = 5, { file = errorsFile() } = {}) {
|
|
183
|
+
return readLines(file).slice(-n);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
export function appendTimeline(entry, { file = timelineFile() } = {}) {
|
|
187
|
+
const line = `${fmtShanghaiYMDHMS(new Date())} ${String(entry || "")}\n`;
|
|
188
|
+
ensureDir(dirname(file));
|
|
189
|
+
if (shouldSync(file)) {
|
|
190
|
+
appendFileSync(file, line);
|
|
191
|
+
trimIfOversized(file);
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
appendFile(file, line).catch(() => {}).then(() => trimIfOversizedAsync(file).catch(() => {}));
|
|
195
|
+
}
|
package/src/metrics.js
CHANGED
|
@@ -1,62 +1,62 @@
|
|
|
1
|
-
import { normalizeFullId, toFullId } from "./providers/model-id.js";
|
|
2
|
-
|
|
3
|
-
export { normalizeFullId, toFullId };
|
|
4
|
-
|
|
5
|
-
// 从上游 usage 或文本兜底算 tps / charsPerSec
|
|
6
|
-
export function computeMetrics({ ttfbMs, totalMs, promptTokens, completionTokens, chars }) {
|
|
7
|
-
const ttfb = Number(ttfbMs);
|
|
8
|
-
const total = Number(totalMs);
|
|
9
|
-
const hasTtfb = Number.isFinite(ttfb) && ttfb >= 0;
|
|
10
|
-
const hasTotal = Number.isFinite(total) && total >= 0;
|
|
11
|
-
const completionMs = hasTtfb && hasTotal ? Math.max(0, total - ttfb) : hasTotal ? total : null;
|
|
12
|
-
const comp = Number(completionTokens);
|
|
13
|
-
const hasComp = Number.isFinite(comp) && comp > 0 && Number.isFinite(completionMs) && completionMs > 0;
|
|
14
|
-
const tps = hasComp ? Number((comp / (completionMs / 1000)).toFixed(1)) : null;
|
|
15
|
-
const c = Number(chars);
|
|
16
|
-
const hasChars = Number.isFinite(c) && c > 0 && Number.isFinite(completionMs) && completionMs > 0;
|
|
17
|
-
const charsPerSec = !hasComp && hasChars ? Math.round(c / (completionMs / 1000)) : null;
|
|
18
|
-
return { completionMs, tps, charsPerSec };
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
// 从 Response 文本或 SSE 末帧提取 usage
|
|
22
|
-
export function extractUsageFromJson(parsed) {
|
|
23
|
-
if (!parsed || typeof parsed !== "object") return null;
|
|
24
|
-
const u = parsed.usage;
|
|
25
|
-
if (!u || typeof u !== "object") return null;
|
|
26
|
-
const prompt = Number(u.prompt_tokens ?? u.promptTokens ?? u.input_tokens);
|
|
27
|
-
const comp = Number(u.completion_tokens ?? u.completionTokens ?? u.output_tokens);
|
|
28
|
-
const total = Number(u.total_tokens ?? u.totalTokens);
|
|
29
|
-
const out = {};
|
|
30
|
-
if (Number.isFinite(prompt)) out.prompt_tokens = prompt;
|
|
31
|
-
if (Number.isFinite(comp)) out.completion_tokens = comp;
|
|
32
|
-
if (Number.isFinite(total)) out.total_tokens = total;
|
|
33
|
-
// reasoning_tokens 透传
|
|
34
|
-
const rt = u.completion_tokens_details?.reasoning_tokens ?? u.reasoning_tokens;
|
|
35
|
-
if (Number.isFinite(Number(rt))) out.reasoning_tokens = Number(rt);
|
|
36
|
-
return Object.keys(out).length ? out : null;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
export function extractUsageFromSseText(sseText) {
|
|
40
|
-
if (!sseText) return null;
|
|
41
|
-
const lines = String(sseText).split("\n");
|
|
42
|
-
for (let i = lines.length - 1; i >= 0; i--) {
|
|
43
|
-
const line = lines[i].trim();
|
|
44
|
-
if (!line.startsWith("data:")) continue;
|
|
45
|
-
const data = line.slice(5).trim();
|
|
46
|
-
if (data === "[DONE]") continue;
|
|
47
|
-
try {
|
|
48
|
-
const j = JSON.parse(data);
|
|
49
|
-
if (j.usage) return extractUsageFromJson(j);
|
|
50
|
-
} catch {}
|
|
51
|
-
}
|
|
52
|
-
return null;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
// 归一模型全称:provider + raw -> opencode/xxx
|
|
56
|
-
export function resolveFullId(rawModel, providerHint) {
|
|
57
|
-
const raw = String(rawModel || "").trim();
|
|
58
|
-
if (!raw) return "";
|
|
59
|
-
if (raw.includes("/")) return normalizeFullId(raw);
|
|
60
|
-
const prov = String(providerHint || "opencode").trim() || "opencode";
|
|
61
|
-
return toFullId(prov, raw);
|
|
62
|
-
}
|
|
1
|
+
import { normalizeFullId, toFullId } from "./providers/model-id.js";
|
|
2
|
+
|
|
3
|
+
export { normalizeFullId, toFullId };
|
|
4
|
+
|
|
5
|
+
// 从上游 usage 或文本兜底算 tps / charsPerSec
|
|
6
|
+
export function computeMetrics({ ttfbMs, totalMs, promptTokens, completionTokens, chars }) {
|
|
7
|
+
const ttfb = Number(ttfbMs);
|
|
8
|
+
const total = Number(totalMs);
|
|
9
|
+
const hasTtfb = Number.isFinite(ttfb) && ttfb >= 0;
|
|
10
|
+
const hasTotal = Number.isFinite(total) && total >= 0;
|
|
11
|
+
const completionMs = hasTtfb && hasTotal ? Math.max(0, total - ttfb) : hasTotal ? total : null;
|
|
12
|
+
const comp = Number(completionTokens);
|
|
13
|
+
const hasComp = Number.isFinite(comp) && comp > 0 && Number.isFinite(completionMs) && completionMs > 0;
|
|
14
|
+
const tps = hasComp ? Number((comp / (completionMs / 1000)).toFixed(1)) : null;
|
|
15
|
+
const c = Number(chars);
|
|
16
|
+
const hasChars = Number.isFinite(c) && c > 0 && Number.isFinite(completionMs) && completionMs > 0;
|
|
17
|
+
const charsPerSec = !hasComp && hasChars ? Math.round(c / (completionMs / 1000)) : null;
|
|
18
|
+
return { completionMs, tps, charsPerSec };
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// 从 Response 文本或 SSE 末帧提取 usage
|
|
22
|
+
export function extractUsageFromJson(parsed) {
|
|
23
|
+
if (!parsed || typeof parsed !== "object") return null;
|
|
24
|
+
const u = parsed.usage;
|
|
25
|
+
if (!u || typeof u !== "object") return null;
|
|
26
|
+
const prompt = Number(u.prompt_tokens ?? u.promptTokens ?? u.input_tokens);
|
|
27
|
+
const comp = Number(u.completion_tokens ?? u.completionTokens ?? u.output_tokens);
|
|
28
|
+
const total = Number(u.total_tokens ?? u.totalTokens);
|
|
29
|
+
const out = {};
|
|
30
|
+
if (Number.isFinite(prompt)) out.prompt_tokens = prompt;
|
|
31
|
+
if (Number.isFinite(comp)) out.completion_tokens = comp;
|
|
32
|
+
if (Number.isFinite(total)) out.total_tokens = total;
|
|
33
|
+
// reasoning_tokens 透传
|
|
34
|
+
const rt = u.completion_tokens_details?.reasoning_tokens ?? u.reasoning_tokens;
|
|
35
|
+
if (Number.isFinite(Number(rt))) out.reasoning_tokens = Number(rt);
|
|
36
|
+
return Object.keys(out).length ? out : null;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function extractUsageFromSseText(sseText) {
|
|
40
|
+
if (!sseText) return null;
|
|
41
|
+
const lines = String(sseText).split("\n");
|
|
42
|
+
for (let i = lines.length - 1; i >= 0; i--) {
|
|
43
|
+
const line = lines[i].trim();
|
|
44
|
+
if (!line.startsWith("data:")) continue;
|
|
45
|
+
const data = line.slice(5).trim();
|
|
46
|
+
if (data === "[DONE]") continue;
|
|
47
|
+
try {
|
|
48
|
+
const j = JSON.parse(data);
|
|
49
|
+
if (j.usage) return extractUsageFromJson(j);
|
|
50
|
+
} catch {}
|
|
51
|
+
}
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// 归一模型全称:provider + raw -> opencode/xxx
|
|
56
|
+
export function resolveFullId(rawModel, providerHint) {
|
|
57
|
+
const raw = String(rawModel || "").trim();
|
|
58
|
+
if (!raw) return "";
|
|
59
|
+
if (raw.includes("/")) return normalizeFullId(raw);
|
|
60
|
+
const prov = String(providerHint || "opencode").trim() || "opencode";
|
|
61
|
+
return toFullId(prov, raw);
|
|
62
|
+
}
|
|
@@ -1,65 +1,65 @@
|
|
|
1
|
-
// 模型上下文窗口的四级解析(ADR-0054):/v1/models 富化时唯一真相源。
|
|
2
|
-
// 旧状:顶层 context_length(provider 自报)与 capabilities.context(models.dev)两套并行可能矛盾,
|
|
3
|
-
// 且四层全空时什么都不报;本模块把优先级收死,并保证"双层同源同值"。
|
|
4
|
-
// 纯函数 + env,无 IO 无网络(L1/L2/L4 不依赖目录,冷缓存/目录挂掉照常生效)。
|
|
5
|
-
// L4 方向沿上游 workbuddy2api-hub:"未知报 1M——高估比低估安全"(只作元数据,网关转发不读它)。
|
|
6
|
-
|
|
7
|
-
export const DEFAULT_CONTEXT_FALLBACK = 1_000_000;
|
|
8
|
-
|
|
9
|
-
/** L4 兜底值:`MSLXDFF_MODEL_CONTEXT_FALLBACK` 覆盖;0/负数 = 关闭兜底(回"未知不报"旧口径);非法值按默认 1M。 */
|
|
10
|
-
export function contextFallback() {
|
|
11
|
-
const raw = String(process.env.MSLXDFF_MODEL_CONTEXT_FALLBACK ?? "").trim();
|
|
12
|
-
if (raw === "") return DEFAULT_CONTEXT_FALLBACK;
|
|
13
|
-
const n = Number(raw);
|
|
14
|
-
if (!Number.isFinite(n)) return DEFAULT_CONTEXT_FALLBACK;
|
|
15
|
-
if (n <= 0) return 0;
|
|
16
|
-
const f = Math.floor(n);
|
|
17
|
-
return f > 0 ? f : DEFAULT_CONTEXT_FALLBACK; // (0,1) 被.floor 成 0 会静默变"关兜底",按非法回默认
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
function positiveInt(v) {
|
|
21
|
-
if (typeof v !== "number" && typeof v !== "string") return null; // boolean/数组/对象 = 未自报,不猜
|
|
22
|
-
const n = Number(v);
|
|
23
|
-
return Number.isFinite(n) && n > 0 ? Math.floor(n) : null;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
/** L1 条目自报(first-party 最准)。键序覆盖现网全部自报形状;非数字/0/负数 = 未自报,丢弃而非钳制。 */
|
|
27
|
-
export function firstPartyContextOf(entry) {
|
|
28
|
-
if (!entry || typeof entry !== "object") return null;
|
|
29
|
-
for (const v of [entry.context_length, entry.maxInputTokens, entry.max_input_tokens]) {
|
|
30
|
-
const n = positiveInt(v);
|
|
31
|
-
if (n) return n;
|
|
32
|
-
}
|
|
33
|
-
const cw = entry.contextWindow;
|
|
34
|
-
return positiveInt(cw && typeof cw === "object" ? cw.defaultLength : cw);
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
// L2 内置知识表:给"provider 目录不自报窗口、且值有官方/上游文档出处"的模型补一手。
|
|
38
|
-
// 准入红线(评审过:无据值不得进表——traework 硬编码 131072 正是反面教材,本次已删):
|
|
39
|
-
// - key = **实际传入解析器的条目 id 小写**(现网含裸 id 与 `-free` 后缀形态;填表前必须按此对键,
|
|
40
|
-
// 跨 provider 撞名靠带前缀的 id 天然区分),别照 ADR 初稿的 `prov/raw` canonical 形去猜;
|
|
41
|
-
// - 每条必须带 source 出处注释(文档/上游字段 + 核对日期),给不出出处就让它落 L4 兜底。
|
|
42
|
-
// 首期为空是盘点结论:raccoon/qoder/codearts/workbuddy/globalworkbuddy/qwenwork 系的有据值全部已被 L1 覆盖,
|
|
43
|
-
// 把 L1 复制进表只会制造第二个真相源。等出现"只回 id 的极简目录 provider"之类的真缺口再填。
|
|
44
|
-
export const KNOWN_CONTEXT = Object.freeze({});
|
|
45
|
-
|
|
46
|
-
/** L2 查表(按条目 id 小写);未命中或值非法返回 null。`table` 为测试注入口(缺省用内置表)。 */
|
|
47
|
-
export function knownContextOf(id, table = KNOWN_CONTEXT) {
|
|
48
|
-
const e = table[String(id || "").trim().toLowerCase()];
|
|
49
|
-
return e ? positiveInt(e.context) : null;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
/**
|
|
53
|
-
* 四级解析:L1 自报 > L2 内置表 > L3 models.dev caps > L4 兜底。
|
|
54
|
-
* 返回正整数;env 关闭兜底且 L1–L3 全空时返回 null(调用方两层都不写)。
|
|
55
|
-
* `knownTable` 仅供测试注入假表锁 L2>L3 顺序,生产调用方不传。
|
|
56
|
-
*/
|
|
57
|
-
export function resolveContextLength({ entry, id, caps, knownTable } = {}) {
|
|
58
|
-
const fp = firstPartyContextOf(entry);
|
|
59
|
-
if (fp) return fp;
|
|
60
|
-
const known = knownContextOf(id, knownTable);
|
|
61
|
-
if (known) return known;
|
|
62
|
-
const c = positiveInt(caps?.context);
|
|
63
|
-
if (c) return c;
|
|
64
|
-
return contextFallback() || null;
|
|
65
|
-
}
|
|
1
|
+
// 模型上下文窗口的四级解析(ADR-0054):/v1/models 富化时唯一真相源。
|
|
2
|
+
// 旧状:顶层 context_length(provider 自报)与 capabilities.context(models.dev)两套并行可能矛盾,
|
|
3
|
+
// 且四层全空时什么都不报;本模块把优先级收死,并保证"双层同源同值"。
|
|
4
|
+
// 纯函数 + env,无 IO 无网络(L1/L2/L4 不依赖目录,冷缓存/目录挂掉照常生效)。
|
|
5
|
+
// L4 方向沿上游 workbuddy2api-hub:"未知报 1M——高估比低估安全"(只作元数据,网关转发不读它)。
|
|
6
|
+
|
|
7
|
+
export const DEFAULT_CONTEXT_FALLBACK = 1_000_000;
|
|
8
|
+
|
|
9
|
+
/** L4 兜底值:`MSLXDFF_MODEL_CONTEXT_FALLBACK` 覆盖;0/负数 = 关闭兜底(回"未知不报"旧口径);非法值按默认 1M。 */
|
|
10
|
+
export function contextFallback() {
|
|
11
|
+
const raw = String(process.env.MSLXDFF_MODEL_CONTEXT_FALLBACK ?? "").trim();
|
|
12
|
+
if (raw === "") return DEFAULT_CONTEXT_FALLBACK;
|
|
13
|
+
const n = Number(raw);
|
|
14
|
+
if (!Number.isFinite(n)) return DEFAULT_CONTEXT_FALLBACK;
|
|
15
|
+
if (n <= 0) return 0;
|
|
16
|
+
const f = Math.floor(n);
|
|
17
|
+
return f > 0 ? f : DEFAULT_CONTEXT_FALLBACK; // (0,1) 被.floor 成 0 会静默变"关兜底",按非法回默认
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function positiveInt(v) {
|
|
21
|
+
if (typeof v !== "number" && typeof v !== "string") return null; // boolean/数组/对象 = 未自报,不猜
|
|
22
|
+
const n = Number(v);
|
|
23
|
+
return Number.isFinite(n) && n > 0 ? Math.floor(n) : null;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** L1 条目自报(first-party 最准)。键序覆盖现网全部自报形状;非数字/0/负数 = 未自报,丢弃而非钳制。 */
|
|
27
|
+
export function firstPartyContextOf(entry) {
|
|
28
|
+
if (!entry || typeof entry !== "object") return null;
|
|
29
|
+
for (const v of [entry.context_length, entry.maxInputTokens, entry.max_input_tokens]) {
|
|
30
|
+
const n = positiveInt(v);
|
|
31
|
+
if (n) return n;
|
|
32
|
+
}
|
|
33
|
+
const cw = entry.contextWindow;
|
|
34
|
+
return positiveInt(cw && typeof cw === "object" ? cw.defaultLength : cw);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// L2 内置知识表:给"provider 目录不自报窗口、且值有官方/上游文档出处"的模型补一手。
|
|
38
|
+
// 准入红线(评审过:无据值不得进表——traework 硬编码 131072 正是反面教材,本次已删):
|
|
39
|
+
// - key = **实际传入解析器的条目 id 小写**(现网含裸 id 与 `-free` 后缀形态;填表前必须按此对键,
|
|
40
|
+
// 跨 provider 撞名靠带前缀的 id 天然区分),别照 ADR 初稿的 `prov/raw` canonical 形去猜;
|
|
41
|
+
// - 每条必须带 source 出处注释(文档/上游字段 + 核对日期),给不出出处就让它落 L4 兜底。
|
|
42
|
+
// 首期为空是盘点结论:raccoon/qoder/codearts/workbuddy/globalworkbuddy/qwenwork 系的有据值全部已被 L1 覆盖,
|
|
43
|
+
// 把 L1 复制进表只会制造第二个真相源。等出现"只回 id 的极简目录 provider"之类的真缺口再填。
|
|
44
|
+
export const KNOWN_CONTEXT = Object.freeze({});
|
|
45
|
+
|
|
46
|
+
/** L2 查表(按条目 id 小写);未命中或值非法返回 null。`table` 为测试注入口(缺省用内置表)。 */
|
|
47
|
+
export function knownContextOf(id, table = KNOWN_CONTEXT) {
|
|
48
|
+
const e = table[String(id || "").trim().toLowerCase()];
|
|
49
|
+
return e ? positiveInt(e.context) : null;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* 四级解析:L1 自报 > L2 内置表 > L3 models.dev caps > L4 兜底。
|
|
54
|
+
* 返回正整数;env 关闭兜底且 L1–L3 全空时返回 null(调用方两层都不写)。
|
|
55
|
+
* `knownTable` 仅供测试注入假表锁 L2>L3 顺序,生产调用方不传。
|
|
56
|
+
*/
|
|
57
|
+
export function resolveContextLength({ entry, id, caps, knownTable } = {}) {
|
|
58
|
+
const fp = firstPartyContextOf(entry);
|
|
59
|
+
if (fp) return fp;
|
|
60
|
+
const known = knownContextOf(id, knownTable);
|
|
61
|
+
if (known) return known;
|
|
62
|
+
const c = positiveInt(caps?.context);
|
|
63
|
+
if (c) return c;
|
|
64
|
+
return contextFallback() || null;
|
|
65
|
+
}
|