mslxdff 0.1.203 → 0.1.208

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.
Files changed (332) hide show
  1. package/README.md +3 -3
  2. package/bin/mslxdff.js +1 -1
  3. package/docs/cli_help_mini.md +147 -0
  4. package/package.json +38 -38
  5. package/plugins/README.md +14 -14
  6. package/plugins/prefer-model.mjs.example +14 -14
  7. package/src/anthropic/canonical-json.js +55 -0
  8. package/src/anthropic/count-tokens.js +61 -61
  9. package/src/anthropic/request-body.js +183 -126
  10. package/src/anthropic/request-shape.js +60 -0
  11. package/src/anthropic/session-id.js +58 -0
  12. package/src/anthropic/shared.js +18 -18
  13. package/src/anthropic/sse-aggregate.js +56 -56
  14. package/src/anthropic/tool-media.js +75 -0
  15. package/src/anthropic/translate.js +296 -287
  16. package/src/anthropic/web-search.js +361 -361
  17. package/src/api-kind.js +31 -31
  18. package/src/autostart.js +288 -288
  19. package/src/bench/probe.js +1 -1
  20. package/src/bench/runner.js +1 -1
  21. package/src/bench/via-probe.js +1 -1
  22. package/src/bench/via.js +144 -144
  23. package/src/bench/workbuddy-bench.js +1 -1
  24. package/src/chat/config.js +11 -11
  25. package/src/chat/cooling.js +81 -81
  26. package/src/chat/direct.js +58 -58
  27. package/src/chat/index.js +6 -6
  28. package/src/chat/spinner.js +38 -38
  29. package/src/chat/sse.js +69 -69
  30. package/src/chat/stats.js +247 -247
  31. package/src/chat/store.js +48 -48
  32. package/src/chat/tool-handlers.js +67 -67
  33. package/src/chat/tools.js +1 -1
  34. package/src/chat/upstream.js +59 -59
  35. package/src/chat-pipeline/auto-race.js +158 -158
  36. package/src/chat-pipeline/empty-turn.js +147 -147
  37. package/src/chat-pipeline/engine.js +17 -17
  38. package/src/chat-pipeline/index.js +8 -40
  39. package/src/chat-pipeline/serial-trial.js +279 -283
  40. package/src/chat-pipeline/terminal-verdict.js +132 -147
  41. package/src/chooser.js +54 -54
  42. package/src/claude-desktop/apply.js +101 -101
  43. package/src/claude-desktop/context1m.js +38 -38
  44. package/src/claude-desktop/doctor.js +146 -146
  45. package/src/claude-desktop/slots.js +152 -152
  46. package/src/cli/bootstrap.js +1 -1
  47. package/src/cli/commands/claude-desktop.js +284 -284
  48. package/src/cli/commands/daemon.js +231 -231
  49. package/src/cli/commands/model/list-live.js +22 -22
  50. package/src/cli/commands/model/list-providers.js +76 -76
  51. package/src/cli/commands/model/list-render.js +77 -77
  52. package/src/cli/commands/model/list-sort.js +52 -52
  53. package/src/cli/commands/model/list-tty.js +76 -76
  54. package/src/cli/commands/model/list.js +166 -166
  55. package/src/cli/commands/model/live-models.js +24 -24
  56. package/src/cli/commands/model/stats.js +43 -43
  57. package/src/cli/commands/model/status.js +47 -47
  58. package/src/cli/commands/model.js +51 -51
  59. package/src/cli/commands/provider/add.js +108 -108
  60. package/src/cli/commands/provider/allowlist.js +99 -99
  61. package/src/cli/commands/provider/cline-free.js +99 -99
  62. package/src/cli/commands/provider/cline-login.js +218 -218
  63. package/src/cli/commands/provider/cline-quota.js +65 -65
  64. package/src/cli/commands/provider/codearts-login.js +45 -45
  65. package/src/cli/commands/provider/config.js +119 -119
  66. package/src/cli/commands/provider/globalqwenwork-login.js +126 -126
  67. package/src/cli/commands/provider/keys-guard.js +50 -50
  68. package/src/cli/commands/provider/keys.js +161 -161
  69. package/src/cli/commands/provider/qoder-checkin.js +81 -81
  70. package/src/cli/commands/provider/qoder-login.js +102 -102
  71. package/src/cli/commands/provider/raccoon-login.js +180 -180
  72. package/src/cli/commands/provider/raccoon-password.js +162 -162
  73. package/src/cli/commands/provider/raccoon-quota.js +109 -109
  74. package/src/cli/commands/provider/traework-checkin.js +144 -144
  75. package/src/cli/commands/provider/traework-login.js +164 -164
  76. package/src/cli/commands/provider/workbuddy-login.js +153 -153
  77. package/src/cli/commands/provider/zcode-login.js +77 -77
  78. package/src/cli/commands/provider/zcode-quota.js +55 -55
  79. package/src/cli/commands/provider.js +1 -1
  80. package/src/cli/commands/stats.js +158 -149
  81. package/src/cli/commands/system.js +261 -261
  82. package/src/cli/commands/timezone.js +47 -47
  83. package/src/cli/commands/use-group.js +60 -60
  84. package/src/cli/commands/v6-pool.js +123 -0
  85. package/src/cli/commands/workbuddy.js +189 -189
  86. package/src/cli/format.js +141 -141
  87. package/src/cli/group-helpers.js +70 -70
  88. package/src/cli/help.js +80 -80
  89. package/src/cli/index.js +73 -70
  90. package/src/cli/interactive.js +121 -121
  91. package/src/cli/join-core.js +99 -99
  92. package/src/cli/join-wizard.js +175 -175
  93. package/src/cli/policy.js +136 -136
  94. package/src/cli/provider-row.js +126 -126
  95. package/src/cli/util.js +24 -24
  96. package/src/compat.js +53 -53
  97. package/src/events.js +38 -38
  98. package/src/free-lane.js +131 -131
  99. package/src/free-watcher.js +63 -63
  100. package/src/groups.js +262 -262
  101. package/src/logs.js +195 -195
  102. package/src/metrics.js +71 -62
  103. package/src/model-capabilities/context-lookup.js +65 -65
  104. package/src/model-capabilities/merge.js +142 -142
  105. package/src/model-trace.js +187 -187
  106. package/src/models.js +1 -1
  107. package/src/opencode-identity.js +72 -72
  108. package/src/peers.js +254 -254
  109. package/src/plugins.js +107 -107
  110. package/src/providers/AGENTS.md +49 -0
  111. package/src/providers/base.js +164 -164
  112. package/src/providers/classify.js +22 -22
  113. package/src/providers/cline/allowlist-sync.js +93 -93
  114. package/src/providers/cline/free-catalog.js +71 -71
  115. package/src/providers/cline/headers.js +37 -37
  116. package/src/providers/cline/index.js +130 -130
  117. package/src/providers/cline/models.js +150 -150
  118. package/src/providers/cline/usage.js +274 -274
  119. package/src/providers/codearts/auth-pool.js +148 -148
  120. package/src/providers/codearts/concurrency.js +34 -34
  121. package/src/providers/codearts/const.js +65 -65
  122. package/src/providers/codearts/dpop.js +95 -95
  123. package/src/providers/codearts/index.js +175 -175
  124. package/src/providers/codearts/login.js +214 -214
  125. package/src/providers/codearts/models.js +182 -182
  126. package/src/providers/codearts/sign.js +96 -96
  127. package/src/providers/codearts/sse.js +204 -204
  128. package/src/providers/codearts/stream.js +209 -209
  129. package/src/providers/codearts/sts.js +124 -124
  130. package/src/providers/codearts.js +1 -1
  131. package/src/providers/dispatcher.js +146 -146
  132. package/src/providers/generic.js +156 -156
  133. package/src/providers/globalqwenwork/account-store.js +141 -141
  134. package/src/providers/globalqwenwork/constants.js +73 -73
  135. package/src/providers/globalqwenwork/crypto.js +2 -2
  136. package/src/providers/globalqwenwork/http.js +2 -2
  137. package/src/providers/globalqwenwork/index.js +331 -331
  138. package/src/providers/globalqwenwork/payload.js +147 -147
  139. package/src/providers/globalqwenwork/rsa.js +2 -2
  140. package/src/providers/globalqwenwork/sse.js +2 -2
  141. package/src/providers/globalqwenwork/stream.js +2 -2
  142. package/src/providers/globalqwenwork.js +1 -1
  143. package/src/providers/globalworkbuddy/account-store.js +198 -198
  144. package/src/providers/globalworkbuddy/auth.js +97 -97
  145. package/src/providers/globalworkbuddy/catalog.js +138 -138
  146. package/src/providers/globalworkbuddy/constants.js +73 -73
  147. package/src/providers/globalworkbuddy/device-auth.js +120 -120
  148. package/src/providers/globalworkbuddy/errors.js +98 -98
  149. package/src/providers/globalworkbuddy/headers.js +89 -89
  150. package/src/providers/globalworkbuddy/models.js +96 -96
  151. package/src/providers/globalworkbuddy/payload.js +80 -80
  152. package/src/providers/index.js +5 -5
  153. package/src/providers/model-id.js +172 -172
  154. package/src/providers/opencode.js +19 -19
  155. package/src/providers/openrouter.js +31 -31
  156. package/src/providers/qoder/account-store.js +106 -106
  157. package/src/providers/qoder/aggregate.js +60 -60
  158. package/src/providers/qoder/baseprompt.json +591 -591
  159. package/src/providers/qoder/checkin.js +160 -160
  160. package/src/providers/qoder/constants.js +44 -44
  161. package/src/providers/qoder/encode.js +41 -41
  162. package/src/providers/qoder/fingerprint.js +38 -38
  163. package/src/providers/qoder/models.js +77 -77
  164. package/src/providers/qoder/oauth.js +84 -84
  165. package/src/providers/qoder/payload.js +162 -162
  166. package/src/providers/qoder/request.js +27 -27
  167. package/src/providers/qoder/session.js +134 -134
  168. package/src/providers/qoder/sticky.js +38 -38
  169. package/src/providers/qoder/stream.js +116 -116
  170. package/src/providers/qoder.js +1 -1
  171. package/src/providers/raccoon/account-store.js +216 -216
  172. package/src/providers/raccoon/auth.js +67 -67
  173. package/src/providers/raccoon/chat.js +228 -228
  174. package/src/providers/raccoon/const.js +180 -180
  175. package/src/providers/raccoon/credits.js +185 -185
  176. package/src/providers/raccoon/crypto.js +43 -43
  177. package/src/providers/raccoon/envelope.js +31 -31
  178. package/src/providers/raccoon/headers.js +45 -45
  179. package/src/providers/raccoon/index.js +216 -216
  180. package/src/providers/raccoon/login.js +279 -279
  181. package/src/providers/raccoon/models.js +160 -160
  182. package/src/providers/raccoon/password-login.js +102 -102
  183. package/src/providers/raccoon/qr-codewords.js +115 -115
  184. package/src/providers/raccoon/qr.js +236 -236
  185. package/src/providers/raccoon/sse.js +135 -135
  186. package/src/providers/responses-channel.js +120 -120
  187. package/src/providers/share-keys.js +53 -53
  188. package/src/providers/traework/account-store.js +234 -234
  189. package/src/providers/traework/chat.js +217 -217
  190. package/src/providers/traework/checkin.js +77 -77
  191. package/src/providers/traework/constants.js +41 -41
  192. package/src/providers/traework/errors.js +52 -52
  193. package/src/providers/traework/headers.js +79 -79
  194. package/src/providers/traework/index.js +132 -132
  195. package/src/providers/traework/market-identity.js +116 -116
  196. package/src/providers/traework/models.js +98 -98
  197. package/src/providers/traework/payload.js +86 -86
  198. package/src/providers/traework/sse.js +184 -184
  199. package/src/providers/traework/token.js +52 -52
  200. package/src/providers/traework.js +2 -2
  201. package/src/providers/workbuddy/account-store.js +186 -186
  202. package/src/providers/workbuddy/auth.js +158 -158
  203. package/src/providers/workbuddy/balance.js +86 -86
  204. package/src/providers/workbuddy/cat-travel.js +125 -125
  205. package/src/providers/workbuddy/checkin.js +75 -75
  206. package/src/providers/workbuddy/desktop-info.js +78 -78
  207. package/src/providers/workbuddy/growth-api.js +67 -67
  208. package/src/providers/workbuddy/growth-plans.js +63 -63
  209. package/src/providers/workbuddy/growth.js +181 -181
  210. package/src/providers/workbuddy/index.js +217 -217
  211. package/src/providers/workbuddy/models.js +126 -126
  212. package/src/providers/workbuddy/payload.js +141 -141
  213. package/src/providers/workbuddy/rotation-log.js +83 -83
  214. package/src/providers/workbuddy/sanitize-tools.js +61 -61
  215. package/src/providers/workbuddy/sdk-chat.js +26 -26
  216. package/src/providers/workbuddy-balance.js +78 -78
  217. package/src/providers/zcode/account-store.js +129 -129
  218. package/src/providers/zcode/auth.js +28 -28
  219. package/src/providers/zcode/chat.js +179 -179
  220. package/src/providers/zcode/const.js +54 -54
  221. package/src/providers/zcode/context-shape.js +162 -162
  222. package/src/providers/zcode/headers.js +93 -93
  223. package/src/providers/zcode/index.js +124 -124
  224. package/src/providers/zcode/models.js +65 -65
  225. package/src/providers/zcode/oauth.js +120 -120
  226. package/src/providers/zcode/quota.js +176 -176
  227. package/src/providers/zcode/sse.js +196 -196
  228. package/src/readline-compat.js +25 -25
  229. package/src/responses/translate.js +311 -311
  230. package/src/routes/AGENTS.md +37 -0
  231. package/src/routes/chat/broadband-handler.js +72 -72
  232. package/src/routes/chat/empty-turn-report.js +31 -31
  233. package/src/routes/chat/exhausted-handler.js +87 -87
  234. package/src/routes/chat/hedge-handler.js +130 -130
  235. package/src/routes/chat/index.js +1 -1
  236. package/src/routes/chat/local-handler.js +63 -63
  237. package/src/routes/chat/peer-handler.js +81 -81
  238. package/src/routes/chat.js +3 -3
  239. package/src/routes/fallback.js +88 -88
  240. package/src/routes/groups-relay.js +214 -214
  241. package/src/routes/groups.js +122 -122
  242. package/src/routes/helpers.js +93 -83
  243. package/src/routes/index.js +147 -147
  244. package/src/routes/messages-route.js +375 -365
  245. package/src/routes/peers.js +10 -2
  246. package/src/routes/relay-queue.js +181 -181
  247. package/src/routes/relay.js +60 -60
  248. package/src/routes/responses-route.js +213 -213
  249. package/src/routes/stream-body.js +84 -84
  250. package/src/routes/stream-hold.js +337 -337
  251. package/src/routes/stream-scan.js +366 -366
  252. package/src/routes.js +3 -3
  253. package/src/runtime/auto-update.js +113 -113
  254. package/src/runtime/broadband-stream.js +79 -79
  255. package/src/runtime/broadband.js +103 -103
  256. package/src/runtime/group-sync.js +81 -81
  257. package/src/runtime/keep-warm.js +120 -120
  258. package/src/runtime/lifecycle-forensics.js +116 -116
  259. package/src/runtime/lifecycle-log.js +87 -87
  260. package/src/runtime/log-stamp.js +73 -73
  261. package/src/runtime/proc-info.js +137 -137
  262. package/src/runtime/qoder-checkin.js +93 -93
  263. package/src/runtime/raccoon-checkin.js +126 -126
  264. package/src/runtime/server-lifecycle.js +189 -189
  265. package/src/runtime/traework-checkin.js +118 -118
  266. package/src/runtime/workbuddy-checkin.js +120 -120
  267. package/src/runtime/workbuddy-growth.js +133 -133
  268. package/src/state/facade.js +58 -58
  269. package/src/state/memory.js +162 -162
  270. package/src/state/merge.js +25 -25
  271. package/src/state/migrations/cline-unify.js +94 -94
  272. package/src/state/migrations.js +33 -33
  273. package/src/state/persist.js +42 -42
  274. package/src/state/provider-config.js +142 -142
  275. package/src/state/schemas/group.js +31 -31
  276. package/src/state/schemas/model.js +94 -94
  277. package/src/state/schemas/peer.js +31 -31
  278. package/src/state/schemas/port.js +10 -10
  279. package/src/state/schemas/provider.js +202 -202
  280. package/src/state/schemas/timezone.js +70 -70
  281. package/src/state/schemas/token.js +48 -48
  282. package/src/state/schemas/use-group.js +102 -102
  283. package/src/state/store.js +176 -176
  284. package/src/state.js +1 -1
  285. package/src/sync-claude-desktop.js +234 -234
  286. package/src/sync-claude.js +152 -152
  287. package/src/sync-codex.js +116 -116
  288. package/src/sync-workbuddy.js +158 -158
  289. package/src/talk-full-redact.js +129 -129
  290. package/src/talk-full.js +131 -131
  291. package/src/talk-log.js +257 -257
  292. package/src/text-tokens.js +52 -52
  293. package/src/thinking-effort.js +67 -67
  294. package/src/time.js +106 -106
  295. package/src/timeline.js +26 -26
  296. package/src/transport/index.js +1 -1
  297. package/src/upstream-engine/index.js +74 -74
  298. package/src/upstream-engine/mode.js +15 -15
  299. package/src/upstream-engine/sdk/attempt.js +221 -221
  300. package/src/upstream-engine/sdk/chat.js +28 -28
  301. package/src/upstream-engine/sdk/convert.js +192 -192
  302. package/src/upstream-engine/sdk/diagnose.js +62 -62
  303. package/src/upstream-engine/sdk/dispatch.js +55 -55
  304. package/src/upstream-engine/sdk/responses.js +176 -176
  305. package/src/upstream-engine/sdk/sse.js +166 -166
  306. package/src/upstream-responses.js +305 -305
  307. package/src/usage/cache-fields.js +58 -0
  308. package/src/usage/record.js +109 -106
  309. package/src/usage/report.js +209 -195
  310. package/src/v6pool/claim.js +157 -157
  311. package/src/v6pool/config.js +65 -65
  312. package/src/v6pool/detect.js +130 -130
  313. package/src/v6pool/index.js +152 -152
  314. package/src/v6pool/prefix.js +140 -140
  315. package/src/v6pool/probe.js +40 -40
  316. package/src/v6pool/report.js +104 -104
  317. package/src/v6pool/rotate.js +263 -263
  318. package/src/v6pool/selftest.js +154 -0
  319. package/src/warp-pool/config.js +74 -74
  320. package/src/warp-pool/health.js +106 -106
  321. package/src/warp-pool/identity.js +141 -141
  322. package/src/warp-pool/index.js +358 -358
  323. package/src/warp-pool/keys.js +29 -29
  324. package/src/warp-pool/nodes.js +95 -95
  325. package/src/warp-pool/ports.js +160 -160
  326. package/src/warp-pool/register.js +113 -113
  327. package/src/warp-pool/report.js +232 -232
  328. package/src/warp-pool/revive.js +168 -168
  329. package/src/warp-pool/rotate.js +214 -214
  330. package/src/warp-pool/strategy.js +43 -43
  331. package/src/warp-pool/verdict.js +69 -69
  332. package/src/warp-pool/wireproxy.js +162 -162
@@ -1,149 +1,158 @@
1
- // `-stats` 模型用量报表:每窗口每模型 token 消耗 + 首字/总耗时/速度。默认窗口 = 今日 0 点至现在,--hours N 切近 N 小时滚动。
2
- // 数据来自 src/usage/(逐请求 JSONL),不是 state.json 的终生 EMA —— 见 .scratch/stats-report/SPEC.md
3
- import { usageReport } from "../../usage/report.js";
4
- import { usageEnabled, usageKeepDays } from "../../usage/record.js";
5
-
6
- const FLAGS = ["-stats", "--stats"];
7
-
8
- export function isStatsFlag(args) {
9
- return FLAGS.some((f) => args.includes(f));
10
- }
11
-
12
- // 默认窗口起点 = 本地当日 0 点(setHours 归零,随系统本地时区)。
13
- export function startOfTodayMs(now = Date.now()) {
14
- const d = new Date(now);
15
- d.setHours(0, 0, 0, 0);
16
- return d.getTime();
17
- }
18
-
19
- export function parseStatsArgs(args) {
20
- const hoursIdx = args.findIndex((a) => a === "--hours" || a === "-hours");
21
- const rawHours = hoursIdx >= 0 ? Number(args[hoursIdx + 1]) : NaN;
22
- // null = 默认「今日 00:00 至今」窗口;显式但非法(非数/≤0)的 --hours 同样视为 null,不再回退 24h
23
- const hours = Number.isFinite(rawHours) && Math.floor(rawHours) >= 1 ? Math.min(168, Math.floor(rawHours)) : null; // 0<N<1 折成 0 会造成「近 0h 标签配 24h 数据」,与非法同路走当日窗口
24
- const modelIdx = args.findIndex((a) => a === "--model" || a === "-model");
25
- const rawModel = modelIdx >= 0 && args[modelIdx + 1] && !String(args[modelIdx + 1]).startsWith("-") ? args[modelIdx + 1] : null;
26
- return { hours, model: rawModel, json: args.includes("--json") };
27
- }
28
-
29
- function fmtTok(n) {
30
- // 先判未知:Number(null) || 0 会把「未知」静默渲染成 0,思考列的 — 就永远出不来
31
- const v = Number(n);
32
- if (n == null || !Number.isFinite(v)) return "—";
33
- if (v < 1000) return String(v);
34
- if (v < 1_000_000) return `${(v / 1000).toFixed(1)}k`;
35
- return `${(v / 1_000_000).toFixed(2)}M`;
36
- }
37
-
38
- function fmtMs(v) {
39
- if (v == null || !Number.isFinite(v)) return "—";
40
- return v < 1000 ? `${v}ms` : `${(v / 1000).toFixed(1)}s`;
41
- }
42
-
43
- function fmtTps(v) {
44
- return v == null || !Number.isFinite(v) ? "—" : `${v} tok/s`;
45
- }
46
-
47
- // 中文字符按 2 列宽算,否则表格错位。
48
- function width(value) {
49
- let w = 0;
50
- for (const ch of String(value ?? "")) w += /[\u1100-\u115F\u2E80-\uA4CF\uAC00-\uD7A3\uF900-\uFAFF\uFE30-\uFE4F\uFF00-\uFF60\uFFE0-\uFFE6]/.test(ch) ? 2 : 1;
51
- return w;
52
- }
53
-
54
- function padCell(value, w, align = "left") {
55
- const text = String(value ?? "");
56
- const padding = " ".repeat(Math.max(0, w - width(text)));
57
- return align === "right" ? padding + text : text + padding;
58
- }
59
-
60
- function renderTable(headers, rows, aligns = []) {
61
- const matrix = [headers, ...rows];
62
- const widths = headers.map((_, col) => Math.max(...matrix.map((row) => width(row[col]))));
63
- const rule = (left, middle, right) => left + widths.map((w) => "─".repeat(w + 2)).join(middle) + right;
64
- const line = (row) => `│${row.map((cell, col) => ` ${padCell(cell, widths[col], aligns[col])} `).join("│")}│`;
65
- return [
66
- rule("┌", "┬", "┐"),
67
- line(headers),
68
- rule("├", "┼", "┤"),
69
- ...rows.map(line),
70
- rule("└", "┴", "┘"),
71
- ].join("\n");
72
- }
73
-
74
- function tokenRow(id, r) {
75
- return [id, r.requests, fmtTok(r.promptTokens), fmtTok(r.completionTokens), reasoningCell(r), fmtTok(r.totalTokens)];
76
- }
77
-
78
- // 思考列的四态渲染:~ 只跟估算/混合来源;none(未知)显示 —,绝不显示成 0 冒充「不思考」
79
- function reasoningCell(r) {
80
- if (r.reasoningSource === "none") return "—";
81
- const text = fmtTok(r.reasoningTokens);
82
- return r.reasoningSource === "estimated" || r.reasoningSource === "mixed" ? `${text}~` : text;
83
- }
84
-
85
- function performanceRow(id, r) {
86
- const samples = Number(r.streamRequests) || 0;
87
- const ttfb = samples > 0 ? fmtMs(r.avgTtfbMs) : "—"; // 无流式样本时不拿空样本算出的均值糊人
88
- return [id, ttfb, fmtMs(r.avgTotalMs), fmtTps(r.avgTps), `${Number(r.ttfSamples) || 0}/${samples}`];
89
- }
90
-
91
- export function renderStats(report, { hours = null, model = null } = {}) {
92
- const rangeLabel = hours == null ? "今日 00:00 至今" : `近 ${hours}h`;
93
- const lines = [];
94
- const { models, totals } = report;
95
- if (!models.length) {
96
- lines.push(`暂无用量记录(${rangeLabel}${model ? ` · 模型 ${model}` : ""})— 经 8989 网关发一次请求后出现`);
97
- lines.push("提示:mslxdff -status 看当前体检 · mslxdff -log 20 看最近事件");
98
- lines.push("说明:-chat 直连 mimo/big-pickle 不经网关,不计入本表");
99
- return lines.join("\n");
100
- }
101
-
102
- const right = "right";
103
- const tokenRows = [...models.map((m) => tokenRow(m.id, m)), tokenRow("合计", totals)];
104
- const performanceRows = [...models.map((m) => performanceRow(m.id, m)), performanceRow("合计", totals)];
105
- lines.push(`模型用量报告(${rangeLabel}${model ? ` · 筛选 ${model}` : ""})`);
106
- lines.push(`成功请求:${totals.requests} 次 · 模型:${models.length} 个`);
107
- lines.push("");
108
- lines.push("Token 用量");
109
- lines.push(renderTable(["模型", "请求", "输入", "输出", "思考", "合计"], tokenRows, ["left", right, right, right, right, right]));
110
- lines.push("");
111
- lines.push("响应性能");
112
- lines.push(renderTable(["模型", "首字", "总耗时", "速度", "首字样本"], performanceRows, ["left", right, right, right, right]));
113
- lines.push("");
114
- lines.push("说明:首字 = 本次上游尝试 → 网关转发首个真实数据帧(含上游排队/建连/预读等待);非流式请求不进首字。");
115
- lines.push("思考:数值后带 ~ = 按思考字符÷4 估算(上游未上报);— = 未上报且无可估内容;0 = 上游明确上报本轮无思考。");
116
- lines.push("首字样本:n/N = 有首字样本数/流式请求数,N=0 时首字显示 —(样本太少的均值不可当全量真值)。");
117
- lines.push("速度 = 输出 tokens ÷ 生成耗时(总耗时−首字),按窗口加权;只统计成功请求。");
118
- lines.push("口径差异:-status / -model stats 的首字仍取自 state 的终生 EMA(按转发入口量),与本表**不同源**。");
119
- lines.push("范围:只含经 8989 网关的成功请求;不含失败请求和 -chat 直连。");
120
- const keepDays = usageKeepDays();
121
- if (hours != null && hours > keepDays * 24) {
122
- lines.push(`警告:查询窗口 ${hours}h 超过数据保留 ${keepDays} 天,历史可能不完整。`);
123
- } else {
124
- lines.push(`数据保留 ${keepDays} 天;更早的历史已删除。`);
125
- }
126
- lines.push("明细口径:本表按模型聚合,不展开 via、interrupted 和单次 tps。");
127
- lines.push("数值使用 k/M 缩写;需要精确值或机器处理请加 --json。");
128
- lines.push("可调:默认统计今日 0 点至现在 · mslxdff -stats --hours N(近 N 小时滚动,上限 168)| --model <id> | --json");
129
- lines.push("时区:「今日 0 点」按系统本地时区计,与用量日文件的日切同源。");
130
- return lines.join("\n");
131
- }
132
-
133
- export async function handleStats(args) {
134
- if (!isStatsFlag(args)) return false;
135
- const { hours, model, json } = parseStatsArgs(args);
136
- if (!usageEnabled()) {
137
- console.log("用量记录已关闭(MSLXDFF_USAGE_LOG=0)—— 去掉该 env 后重启 daemon 即可恢复采集。");
138
- return true;
139
- }
140
- const now = Date.now();
141
- const since = hours == null ? startOfTodayMs(now) : null; // 缺省/非法 --hours → 当日 0 点起,毫秒精确不漂移
142
- const report = await usageReport({ hours, since, model, now });
143
- if (json) {
144
- console.log(JSON.stringify(report, null, 2));
145
- return true;
146
- }
147
- console.log(renderStats(report, { hours, model }));
148
- return true;
149
- }
1
+ // `-stats` 模型用量报表:每窗口每模型 token 消耗 + 首字/总耗时/速度。默认窗口 = 今日 0 点至现在,--hours N 切近 N 小时滚动。
2
+ // 数据来自 src/usage/(逐请求 JSONL),不是 state.json 的终生 EMA —— 见 .scratch/stats-report/SPEC.md
3
+ import { usageReport } from "../../usage/report.js";
4
+ import { usageEnabled, usageKeepDays } from "../../usage/record.js";
5
+
6
+ const FLAGS = ["-stats", "--stats"];
7
+
8
+ export function isStatsFlag(args) {
9
+ return FLAGS.some((f) => args.includes(f));
10
+ }
11
+
12
+ // 默认窗口起点 = 本地当日 0 点(setHours 归零,随系统本地时区)。
13
+ export function startOfTodayMs(now = Date.now()) {
14
+ const d = new Date(now);
15
+ d.setHours(0, 0, 0, 0);
16
+ return d.getTime();
17
+ }
18
+
19
+ export function parseStatsArgs(args) {
20
+ const hoursIdx = args.findIndex((a) => a === "--hours" || a === "-hours");
21
+ const rawHours = hoursIdx >= 0 ? Number(args[hoursIdx + 1]) : NaN;
22
+ // null = 默认「今日 00:00 至今」窗口;显式但非法(非数/≤0)的 --hours 同样视为 null,不再回退 24h
23
+ const hours = Number.isFinite(rawHours) && Math.floor(rawHours) >= 1 ? Math.min(168, Math.floor(rawHours)) : null; // 0<N<1 折成 0 会造成「近 0h 标签配 24h 数据」,与非法同路走当日窗口
24
+ const modelIdx = args.findIndex((a) => a === "--model" || a === "-model");
25
+ const rawModel = modelIdx >= 0 && args[modelIdx + 1] && !String(args[modelIdx + 1]).startsWith("-") ? args[modelIdx + 1] : null;
26
+ return { hours, model: rawModel, json: args.includes("--json") };
27
+ }
28
+
29
+ function fmtTok(n) {
30
+ // 先判未知:Number(null) || 0 会把「未知」静默渲染成 0,思考列的 — 就永远出不来
31
+ const v = Number(n);
32
+ if (n == null || !Number.isFinite(v)) return "—";
33
+ if (v < 1000) return String(v);
34
+ if (v < 1_000_000) return `${(v / 1000).toFixed(1)}k`;
35
+ return `${(v / 1_000_000).toFixed(2)}M`;
36
+ }
37
+
38
+ function fmtMs(v) {
39
+ if (v == null || !Number.isFinite(v)) return "—";
40
+ return v < 1000 ? `${v}ms` : `${(v / 1000).toFixed(1)}s`;
41
+ }
42
+
43
+ function fmtTps(v) {
44
+ return v == null || !Number.isFinite(v) ? "—" : `${v} tok/s`;
45
+ }
46
+
47
+ // 中文字符按 2 列宽算,否则表格错位。
48
+ function width(value) {
49
+ let w = 0;
50
+ for (const ch of String(value ?? "")) w += /[\u1100-\u115F\u2E80-\uA4CF\uAC00-\uD7A3\uF900-\uFAFF\uFE30-\uFE4F\uFF00-\uFF60\uFFE0-\uFFE6]/.test(ch) ? 2 : 1;
51
+ return w;
52
+ }
53
+
54
+ function padCell(value, w, align = "left") {
55
+ const text = String(value ?? "");
56
+ const padding = " ".repeat(Math.max(0, w - width(text)));
57
+ return align === "right" ? padding + text : text + padding;
58
+ }
59
+
60
+ function renderTable(headers, rows, aligns = []) {
61
+ const matrix = [headers, ...rows];
62
+ const widths = headers.map((_, col) => Math.max(...matrix.map((row) => width(row[col]))));
63
+ const rule = (left, middle, right) => left + widths.map((w) => "─".repeat(w + 2)).join(middle) + right;
64
+ const line = (row) => `│${row.map((cell, col) => ` ${padCell(cell, widths[col], aligns[col])} `).join("│")}│`;
65
+ return [
66
+ rule("┌", "┬", "┐"),
67
+ line(headers),
68
+ rule("├", "┼", "┤"),
69
+ ...rows.map(line),
70
+ rule("└", "┴", "┘"),
71
+ ].join("\n");
72
+ }
73
+
74
+ function tokenRow(id, r) {
75
+ return [id, r.requests, fmtTok(r.promptTokens), fmtTok(r.completionTokens), cacheCell(r), reasoningCell(r), fmtTok(r.totalTokens)];
76
+ }
77
+
78
+ // 思考列的四态渲染:~ 只跟估算/混合来源;none(未知)显示 —,绝不显示成 0 冒充「不思考」
79
+ function reasoningCell(r) {
80
+ if (r.reasoningSource === "none") return "—";
81
+ const text = fmtTok(r.reasoningTokens);
82
+ return r.reasoningSource === "estimated" || r.reasoningSource === "mixed" ? `${text}~` : text;
83
+ }
84
+
85
+ // 缓存命中列:`N (p%)` = 上游上报的缓存读 token ÷ 窗口输入总数(上游 prompt_tokens 含命中)。
86
+ // 窗口内没有任何命中读数(cacheRows=0)显示 —:不拿 0 冒充「这个模型没缓存」。
87
+ function cacheCell(r) {
88
+ if (!r.cacheRows || !(r.cacheReadTokens > 0)) return "—";
89
+ const tokens = fmtTok(r.cacheReadTokens);
90
+ return r.cacheHitRate == null ? tokens : `${tokens} (${(r.cacheHitRate * 100).toFixed(1)}%)`;
91
+ }
92
+
93
+ function performanceRow(id, r) {
94
+ const samples = Number(r.streamRequests) || 0;
95
+ const ttfb = samples > 0 ? fmtMs(r.avgTtfbMs) : "—"; // 无流式样本时不拿空样本算出的均值糊人
96
+ return [id, ttfb, fmtMs(r.avgTotalMs), fmtTps(r.avgTps), `${Number(r.ttfSamples) || 0}/${samples}`];
97
+ }
98
+
99
+ export function renderStats(report, { hours = null, model = null } = {}) {
100
+ const rangeLabel = hours == null ? "今日 00:00 至今" : `近 ${hours}h`;
101
+ const lines = [];
102
+ const { models, totals } = report;
103
+ if (!models.length) {
104
+ lines.push(`暂无用量记录(${rangeLabel}${model ? ` · 模型 ${model}` : ""})— 经 8989 网关发一次请求后出现`);
105
+ lines.push("提示:mslxdff -status 看当前体检 · mslxdff -log 20 看最近事件");
106
+ lines.push("说明:-chat 直连 mimo/big-pickle 不经网关,不计入本表");
107
+ return lines.join("\n");
108
+ }
109
+
110
+ const right = "right";
111
+ const tokenRows = [...models.map((m) => tokenRow(m.id, m)), tokenRow("合计", totals)];
112
+ const performanceRows = [...models.map((m) => performanceRow(m.id, m)), performanceRow("合计", totals)];
113
+ lines.push(`模型用量报告(${rangeLabel}${model ? ` · 筛选 ${model}` : ""})`);
114
+ lines.push(`成功请求:${totals.requests} 次 · 模型:${models.length} 个`);
115
+ lines.push("");
116
+ lines.push("Token 用量");
117
+ lines.push(renderTable(["模型", "请求", "输入", "输出", "缓存命中", "思考", "合计"], tokenRows, ["left", right, right, right, right, right, right]));
118
+ lines.push("");
119
+ lines.push("响应性能");
120
+ lines.push(renderTable(["模型", "首字", "总耗时", "速度", "首字样本"], performanceRows, ["left", right, right, right, right]));
121
+ lines.push("");
122
+ lines.push("说明:首字 = 本次上游尝试 → 网关转发首个真实数据帧(含上游排队/建连/预读等待);非流式请求不进首字。");
123
+ lines.push("思考:数值后带 ~ = 按思考字符÷4 估算(上游未上报);— = 未上报且无可估内容;0 = 上游明确上报本轮无思考。");
124
+ lines.push("首字样本:n/N = 有首字样本数/流式请求数,N=0 时首字显示 —(样本太少的均值不可当全量真值)。");
125
+ lines.push("速度 = 输出 tokens ÷ 生成耗时(总耗时−首字),按窗口加权;只统计成功请求。");
126
+ lines.push("缓存命中:N (p%) = 上游上报的缓存读 token ÷ 窗口输入总数(上游输入总数含命中);— = 窗口内没有命中读数(上游未报或确无命中)。");
127
+ lines.push("口径差异:-status / -model stats 的首字仍取自 state 的终生 EMA(按转发入口量),与本表**不同源**。");
128
+ lines.push("范围:只含经 8989 网关的成功请求;不含失败请求和 -chat 直连。");
129
+ const keepDays = usageKeepDays();
130
+ if (hours != null && hours > keepDays * 24) {
131
+ lines.push(`警告:查询窗口 ${hours}h 超过数据保留 ${keepDays} 天,历史可能不完整。`);
132
+ } else {
133
+ lines.push(`数据保留 ${keepDays} 天;更早的历史已删除。`);
134
+ }
135
+ lines.push("明细口径:本表按模型聚合,不展开 via、interrupted 和单次 tps。");
136
+ lines.push("数值使用 k/M 缩写;需要精确值或机器处理请加 --json。");
137
+ lines.push("可调:默认统计今日 0 点至现在 · mslxdff -stats --hours N(近 N 小时滚动,上限 168)| --model <id> | --json");
138
+ lines.push("时区:「今日 0 点」按系统本地时区计,与用量日文件的日切同源。");
139
+ return lines.join("\n");
140
+ }
141
+
142
+ export async function handleStats(args) {
143
+ if (!isStatsFlag(args)) return false;
144
+ const { hours, model, json } = parseStatsArgs(args);
145
+ if (!usageEnabled()) {
146
+ console.log("用量记录已关闭(MSLXDFF_USAGE_LOG=0)—— 去掉该 env 后重启 daemon 即可恢复采集。");
147
+ return true;
148
+ }
149
+ const now = Date.now();
150
+ const since = hours == null ? startOfTodayMs(now) : null; // 缺省/非法 --hours → 当日 0 点起,毫秒精确不漂移
151
+ const report = await usageReport({ hours, since, model, now });
152
+ if (json) {
153
+ console.log(JSON.stringify(report, null, 2));
154
+ return true;
155
+ }
156
+ console.log(renderStats(report, { hours, model }));
157
+ return true;
158
+ }